##// END OF EJS Templates
go-to search: updated logic of goto switcher...
marcink -
r3556:a75b51f8 default
parent child Browse files
Show More
@@ -1,659 +1,746 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_, 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, 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 .order_by(func.length(Repository.repo_name))\
116 .order_by(Repository.repo_name)\
117 .filter(Repository.archived.isnot(true()))\
115 .filter(Repository.archived.isnot(true()))\
118 .filter(or_(
116 .filter(or_(
119 # generate multiple IN to fix limitation problems
117 # generate multiple IN to fix limitation problems
120 *in_filter_generator(Repository.repo_id, allowed_ids)
118 *in_filter_generator(Repository.repo_id, allowed_ids)
121 ))
119 ))
122
120
121 query = query.order_by(case(
122 [
123 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
124 ],
125 ))
126 query = query.order_by(func.length(Repository.repo_name))
127 query = query.order_by(Repository.repo_name)
128
123 if repo_type:
129 if repo_type:
124 query = query.filter(Repository.repo_type == repo_type)
130 query = query.filter(Repository.repo_type == repo_type)
125
131
126 if name_contains:
132 if name_contains:
127 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
133 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
128 query = query.filter(
134 query = query.filter(
129 Repository.repo_name.ilike(ilike_expression))
135 Repository.repo_name.ilike(ilike_expression))
130 query = query.limit(limit)
136 query = query.limit(limit)
131
137
132 acl_iter = query
138 acl_iter = query
133
139
134 return [
140 return [
135 {
141 {
136 'id': obj.repo_name,
142 'id': obj.repo_name,
137 'value': org_query,
143 'value': org_query,
138 'value_display': obj.repo_name,
144 'value_display': obj.repo_name,
139 'text': obj.repo_name,
145 'text': obj.repo_name,
140 'type': 'repo',
146 'type': 'repo',
141 'repo_id': obj.repo_id,
147 'repo_id': obj.repo_id,
142 'repo_type': obj.repo_type,
148 'repo_type': obj.repo_type,
143 'private': obj.private,
149 'private': obj.private,
144 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
150 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
145 }
151 }
146 for obj in acl_iter]
152 for obj in acl_iter]
147
153
148 def _get_repo_group_list(self, name_contains=None, limit=20):
154 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
149 org_query = name_contains
155 org_query = name_contains
150 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
156 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
151 ['group.read', 'group.write', 'group.admin'],
157 ['group.read', 'group.write', 'group.admin'],
152 cache=False, name_filter=name_contains) or [-1]
158 cache=False, name_filter=name_contains) or [-1]
153
159
154 query = RepoGroup.query()\
160 query = RepoGroup.query()\
155 .order_by(func.length(RepoGroup.group_name))\
156 .order_by(RepoGroup.group_name) \
157 .filter(or_(
161 .filter(or_(
158 # generate multiple IN to fix limitation problems
162 # generate multiple IN to fix limitation problems
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
163 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 ))
164 ))
161
165
166 query = query.order_by(case(
167 [
168 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
169 ],
170 ))
171 query = query.order_by(func.length(RepoGroup.group_name))
172 query = query.order_by(RepoGroup.group_name)
173
162 if name_contains:
174 if name_contains:
163 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
175 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
164 query = query.filter(
176 query = query.filter(
165 RepoGroup.group_name.ilike(ilike_expression))
177 RepoGroup.group_name.ilike(ilike_expression))
166 query = query.limit(limit)
178 query = query.limit(limit)
167
179
168 acl_iter = query
180 acl_iter = query
169
181
170 return [
182 return [
171 {
183 {
172 'id': obj.group_name,
184 'id': obj.group_name,
173 'value': org_query,
185 'value': org_query,
174 'value_display': obj.group_name,
186 'value_display': obj.group_name,
175 'text': obj.group_name,
187 'text': obj.group_name,
176 'type': 'repo_group',
188 'type': 'repo_group',
177 'repo_group_id': obj.group_id,
189 'repo_group_id': obj.group_id,
178 'url': h.route_path(
190 'url': h.route_path(
179 'repo_group_home', repo_group_name=obj.group_name)
191 'repo_group_home', repo_group_name=obj.group_name)
180 }
192 }
181 for obj in acl_iter]
193 for obj in acl_iter]
182
194
183 def _get_user_list(self, name_contains=None, limit=20):
195 def _get_user_list(self, name_contains=None, limit=20):
184 org_query = name_contains
196 org_query = name_contains
185 if not name_contains:
197 if not name_contains:
186 return []
198 return [], False
187
199
188 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
200 # TODO(marcink): should all logged in users be allowed to search others?
201 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
202 if not allowed_user_search:
203 return [], False
204
205 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
189 if len(name_contains) != 1:
206 if len(name_contains) != 1:
190 return []
207 return [], False
208
191 name_contains = name_contains[0]
209 name_contains = name_contains[0]
192
210
193 query = User.query()\
211 query = User.query()\
194 .order_by(func.length(User.username))\
212 .order_by(func.length(User.username))\
195 .order_by(User.username) \
213 .order_by(User.username) \
196 .filter(User.username != User.DEFAULT_USER)
214 .filter(User.username != User.DEFAULT_USER)
197
215
198 if name_contains:
216 if name_contains:
199 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
217 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
200 query = query.filter(
218 query = query.filter(
201 User.username.ilike(ilike_expression))
219 User.username.ilike(ilike_expression))
202 query = query.limit(limit)
220 query = query.limit(limit)
203
221
204 acl_iter = query
222 acl_iter = query
205
223
206 return [
224 return [
207 {
225 {
208 'id': obj.user_id,
226 'id': obj.user_id,
209 'value': org_query,
227 'value': org_query,
210 'value_display': obj.username,
228 'value_display': 'user: `{}`'.format(obj.username),
211 'type': 'user',
229 'type': 'user',
212 'icon_link': h.gravatar_url(obj.email, 30),
230 'icon_link': h.gravatar_url(obj.email, 30),
213 'url': h.route_path(
231 'url': h.route_path(
214 'user_profile', username=obj.username)
232 'user_profile', username=obj.username)
215 }
233 }
216 for obj in acl_iter]
234 for obj in acl_iter], True
217
235
218 def _get_user_groups_list(self, name_contains=None, limit=20):
236 def _get_user_groups_list(self, name_contains=None, limit=20):
219 org_query = name_contains
237 org_query = name_contains
220 if not name_contains:
238 if not name_contains:
221 return []
239 return [], False
222
240
223 name_contains = re.compile('(?:user_group:)(.+)').findall(name_contains)
241 # TODO(marcink): should all logged in users be allowed to search others?
242 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
243 if not allowed_user_search:
244 return [], False
245
246 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
224 if len(name_contains) != 1:
247 if len(name_contains) != 1:
225 return []
248 return [], False
249
226 name_contains = name_contains[0]
250 name_contains = name_contains[0]
227
251
228 query = UserGroup.query()\
252 query = UserGroup.query()\
229 .order_by(func.length(UserGroup.users_group_name))\
253 .order_by(func.length(UserGroup.users_group_name))\
230 .order_by(UserGroup.users_group_name)
254 .order_by(UserGroup.users_group_name)
231
255
232 if name_contains:
256 if name_contains:
233 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
257 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
234 query = query.filter(
258 query = query.filter(
235 UserGroup.users_group_name.ilike(ilike_expression))
259 UserGroup.users_group_name.ilike(ilike_expression))
236 query = query.limit(limit)
260 query = query.limit(limit)
237
261
238 acl_iter = query
262 acl_iter = query
239
263
240 return [
264 return [
241 {
265 {
242 'id': obj.users_group_id,
266 'id': obj.users_group_id,
243 'value': org_query,
267 'value': org_query,
244 'value_display': obj.users_group_name,
268 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
245 'type': 'user_group',
269 'type': 'user_group',
246 'url': h.route_path(
270 'url': h.route_path(
247 'user_group_profile', user_group_name=obj.users_group_name)
271 'user_group_profile', user_group_name=obj.users_group_name)
248 }
272 }
249 for obj in acl_iter]
273 for obj in acl_iter], True
250
274
251 def _get_hash_commit_list(self, auth_user, searcher, query):
275 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
276 repo_name = repo_group_name = None
277 if repo:
278 repo_name = repo.repo_name
279 if repo_group:
280 repo_group_name = repo_group.group_name
281
252 org_query = query
282 org_query = query
253 if not query or len(query) < 3 or not searcher:
283 if not query or len(query) < 3 or not searcher:
254 return []
284 return [], False
255
285
256 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)
257
287
258 if len(commit_hashes) != 1:
288 if len(commit_hashes) != 1:
259 return []
289 return [], False
290
260 commit_hash = commit_hashes[0]
291 commit_hash = commit_hashes[0]
261
292
262 result = searcher.search(
293 result = searcher.search(
263 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
294 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
264 raise_on_exc=False)
295 repo_name, repo_group_name, raise_on_exc=False)
265
296
266 commits = []
297 commits = []
267 for entry in result['results']:
298 for entry in result['results']:
268 repo_data = {
299 repo_data = {
269 'repository_id': entry.get('repository_id'),
300 'repository_id': entry.get('repository_id'),
270 'repository_type': entry.get('repo_type'),
301 'repository_type': entry.get('repo_type'),
271 'repository_name': entry.get('repository'),
302 'repository_name': entry.get('repository'),
272 }
303 }
273
304
274 commit_entry = {
305 commit_entry = {
275 'id': entry['commit_id'],
306 'id': entry['commit_id'],
276 'value': org_query,
307 'value': org_query,
277 'value_display': '`{}` commit: {}'.format(
308 'value_display': '`{}` commit: {}'.format(
278 entry['repository'], entry['commit_id']),
309 entry['repository'], entry['commit_id']),
279 'type': 'commit',
310 'type': 'commit',
280 'repo': entry['repository'],
311 'repo': entry['repository'],
281 'repo_data': repo_data,
312 'repo_data': repo_data,
282
313
283 'url': h.route_path(
314 'url': h.route_path(
284 'repo_commit',
315 'repo_commit',
285 repo_name=entry['repository'], commit_id=entry['commit_id'])
316 repo_name=entry['repository'], commit_id=entry['commit_id'])
286 }
317 }
287
318
288 commits.append(commit_entry)
319 commits.append(commit_entry)
289 return commits
320 return commits, True
290
321
291 def _get_path_list(self, auth_user, searcher, query):
322 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
323 repo_name = repo_group_name = None
324 if repo:
325 repo_name = repo.repo_name
326 if repo_group:
327 repo_group_name = repo_group.group_name
328
292 org_query = query
329 org_query = query
293 if not query or len(query) < 3 or not searcher:
330 if not query or len(query) < 3 or not searcher:
294 return []
331 return [], False
295
332
296 paths_re = re.compile('(?:file:)(.{1,})').findall(query)
333 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
297 if len(paths_re) != 1:
334 if len(paths_re) != 1:
298 return []
335 return [], False
336
299 file_path = paths_re[0]
337 file_path = paths_re[0]
300
338
301 search_path = searcher.escape_specials(file_path)
339 search_path = searcher.escape_specials(file_path)
302 result = searcher.search(
340 result = searcher.search(
303 'file.raw:*{}*'.format(search_path), 'path', auth_user,
341 'file.raw:*{}*'.format(search_path), 'path', auth_user,
304 raise_on_exc=False)
342 repo_name, repo_group_name, raise_on_exc=False)
305
343
306 files = []
344 files = []
307 for entry in result['results']:
345 for entry in result['results']:
308 repo_data = {
346 repo_data = {
309 'repository_id': entry.get('repository_id'),
347 'repository_id': entry.get('repository_id'),
310 'repository_type': entry.get('repo_type'),
348 'repository_type': entry.get('repo_type'),
311 'repository_name': entry.get('repository'),
349 'repository_name': entry.get('repository'),
312 }
350 }
313
351
314 file_entry = {
352 file_entry = {
315 'id': entry['commit_id'],
353 'id': entry['commit_id'],
316 'value': org_query,
354 'value': org_query,
317 'value_display': '`{}` file: {}'.format(
355 'value_display': '`{}` file: {}'.format(
318 entry['repository'], entry['file']),
356 entry['repository'], entry['file']),
319 'type': 'file',
357 'type': 'file',
320 'repo': entry['repository'],
358 'repo': entry['repository'],
321 'repo_data': repo_data,
359 'repo_data': repo_data,
322
360
323 'url': h.route_path(
361 'url': h.route_path(
324 'repo_files',
362 'repo_files',
325 repo_name=entry['repository'], commit_id=entry['commit_id'],
363 repo_name=entry['repository'], commit_id=entry['commit_id'],
326 f_path=entry['file'])
364 f_path=entry['file'])
327 }
365 }
328
366
329 files.append(file_entry)
367 files.append(file_entry)
330 return files
368 return files, True
331
369
332 @LoginRequired()
370 @LoginRequired()
333 @view_config(
371 @view_config(
334 route_name='repo_list_data', request_method='GET',
372 route_name='repo_list_data', request_method='GET',
335 renderer='json_ext', xhr=True)
373 renderer='json_ext', xhr=True)
336 def repo_list_data(self):
374 def repo_list_data(self):
337 _ = self.request.translate
375 _ = self.request.translate
338 self.load_default_context()
376 self.load_default_context()
339
377
340 query = self.request.GET.get('query')
378 query = self.request.GET.get('query')
341 repo_type = self.request.GET.get('repo_type')
379 repo_type = self.request.GET.get('repo_type')
342 log.debug('generating repo list, query:%s, repo_type:%s',
380 log.debug('generating repo list, query:%s, repo_type:%s',
343 query, repo_type)
381 query, repo_type)
344
382
345 res = []
383 res = []
346 repos = self._get_repo_list(query, repo_type=repo_type)
384 repos = self._get_repo_list(query, repo_type=repo_type)
347 if repos:
385 if repos:
348 res.append({
386 res.append({
349 'text': _('Repositories'),
387 'text': _('Repositories'),
350 'children': repos
388 'children': repos
351 })
389 })
352
390
353 data = {
391 data = {
354 'more': False,
392 'more': False,
355 'results': res
393 'results': res
356 }
394 }
357 return data
395 return data
358
396
359 @LoginRequired()
397 @LoginRequired()
360 @view_config(
398 @view_config(
361 route_name='repo_group_list_data', request_method='GET',
399 route_name='repo_group_list_data', request_method='GET',
362 renderer='json_ext', xhr=True)
400 renderer='json_ext', xhr=True)
363 def repo_group_list_data(self):
401 def repo_group_list_data(self):
364 _ = self.request.translate
402 _ = self.request.translate
365 self.load_default_context()
403 self.load_default_context()
366
404
367 query = self.request.GET.get('query')
405 query = self.request.GET.get('query')
368
406
369 log.debug('generating repo group list, query:%s',
407 log.debug('generating repo group list, query:%s',
370 query)
408 query)
371
409
372 res = []
410 res = []
373 repo_groups = self._get_repo_group_list(query)
411 repo_groups = self._get_repo_group_list(query)
374 if repo_groups:
412 if repo_groups:
375 res.append({
413 res.append({
376 'text': _('Repository Groups'),
414 'text': _('Repository Groups'),
377 'children': repo_groups
415 'children': repo_groups
378 })
416 })
379
417
380 data = {
418 data = {
381 'more': False,
419 'more': False,
382 'results': res
420 'results': res
383 }
421 }
384 return data
422 return data
385
423
386 def _get_default_search_queries(self, search_context, searcher, query):
424 def _get_default_search_queries(self, search_context, searcher, query):
387 if not searcher:
425 if not searcher:
388 return []
426 return []
389
427
390 is_es_6 = searcher.is_es_6
428 is_es_6 = searcher.is_es_6
391
429
392 queries = []
430 queries = []
393 repo_group_name, repo_name, repo_context = None, None, None
431 repo_group_name, repo_name, repo_context = None, None, None
394
432
395 # repo group context
433 # repo group context
396 if search_context.get('search_context[repo_group_name]'):
434 if search_context.get('search_context[repo_group_name]'):
397 repo_group_name = search_context.get('search_context[repo_group_name]')
435 repo_group_name = search_context.get('search_context[repo_group_name]')
398 if search_context.get('search_context[repo_name]'):
436 if search_context.get('search_context[repo_name]'):
399 repo_name = search_context.get('search_context[repo_name]')
437 repo_name = search_context.get('search_context[repo_name]')
400 repo_context = search_context.get('search_context[repo_view_type]')
438 repo_context = search_context.get('search_context[repo_view_type]')
401
439
402 if is_es_6 and repo_name:
440 if is_es_6 and repo_name:
403 # files
441 # files
404 def query_modifier():
442 def query_modifier():
405 qry = query
443 qry = query
406 return {'q': qry, 'type': 'content'}
444 return {'q': qry, 'type': 'content'}
407 label = u'File search for `{}` in this repository.'.format(query)
445 label = u'File search for `{}` in this repository.'.format(query)
408 queries.append(
446 file_qry = {
409 {
410 'id': -10,
447 'id': -10,
411 'value': query,
448 'value': query,
412 'value_display': label,
449 'value_display': label,
413 'type': 'search',
450 'type': 'search',
414 'url': h.route_path('search_repo',
451 'url': h.route_path('search_repo',
415 repo_name=repo_name,
452 repo_name=repo_name,
416 _query=query_modifier())
453 _query=query_modifier())
417 }
454 }
418 )
419
455
420 # commits
456 # commits
421 def query_modifier():
457 def query_modifier():
422 qry = query
458 qry = query
423 return {'q': qry, 'type': 'commit'}
459 return {'q': qry, 'type': 'commit'}
424
460
425 label = u'Commit search for `{}` in this repository.'.format(query)
461 label = u'Commit search for `{}` in this repository.'.format(query)
426 queries.append(
462 commit_qry = {
427 {
428 'id': -20,
463 'id': -20,
429 'value': query,
464 'value': query,
430 'value_display': label,
465 'value_display': label,
431 'type': 'search',
466 'type': 'search',
432 'url': h.route_path('search_repo',
467 'url': h.route_path('search_repo',
433 repo_name=repo_name,
468 repo_name=repo_name,
434 _query=query_modifier())
469 _query=query_modifier())
435 }
470 }
436 )
471
472 if repo_context in ['commit', 'changelog']:
473 queries.extend([commit_qry, file_qry])
474 elif repo_context in ['files', 'summary']:
475 queries.extend([file_qry, commit_qry])
476 else:
477 queries.extend([commit_qry, file_qry])
437
478
438 elif is_es_6 and repo_group_name:
479 elif is_es_6 and repo_group_name:
439 # files
480 # files
440 def query_modifier():
481 def query_modifier():
441 qry = query
482 qry = query
442 return {'q': qry, 'type': 'content'}
483 return {'q': qry, 'type': 'content'}
443
484
444 label = u'File search for `{}` in this repository group'.format(query)
485 label = u'File search for `{}` in this repository group'.format(query)
445 queries.append(
486 file_qry = {
446 {
447 'id': -30,
487 'id': -30,
448 'value': query,
488 'value': query,
449 'value_display': label,
489 'value_display': label,
450 'type': 'search',
490 'type': 'search',
451 'url': h.route_path('search_repo_group',
491 'url': h.route_path('search_repo_group',
452 repo_group_name=repo_group_name,
492 repo_group_name=repo_group_name,
453 _query=query_modifier())
493 _query=query_modifier())
454 }
494 }
455 )
456
495
457 # commits
496 # commits
458 def query_modifier():
497 def query_modifier():
459 qry = query
498 qry = query
460 return {'q': qry, 'type': 'commit'}
499 return {'q': qry, 'type': 'commit'}
461
500
462 label = u'Commit search for `{}` in this repository group'.format(query)
501 label = u'Commit search for `{}` in this repository group'.format(query)
463 queries.append(
502 commit_qry = {
464 {
465 'id': -40,
503 'id': -40,
466 'value': query,
504 'value': query,
467 'value_display': label,
505 'value_display': label,
468 'type': 'search',
506 'type': 'search',
469 'url': h.route_path('search_repo_group',
507 'url': h.route_path('search_repo_group',
470 repo_group_name=repo_group_name,
508 repo_group_name=repo_group_name,
471 _query=query_modifier())
509 _query=query_modifier())
472 }
510 }
473 )
474
511
512 if repo_context in ['commit', 'changelog']:
513 queries.extend([commit_qry, file_qry])
514 elif repo_context in ['files', 'summary']:
515 queries.extend([file_qry, commit_qry])
516 else:
517 queries.extend([commit_qry, file_qry])
518
519 # Global, not scoped
475 if not queries:
520 if not queries:
476 queries.append(
521 queries.append(
477 {
522 {
478 'id': -1,
523 'id': -1,
479 'value': query,
524 'value': query,
480 'value_display': u'File search for: `{}`'.format(query),
525 'value_display': u'File search for: `{}`'.format(query),
481 'type': 'search',
526 'type': 'search',
482 'url': h.route_path('search',
527 'url': h.route_path('search',
483 _query={'q': query, 'type': 'content'})
528 _query={'q': query, 'type': 'content'})
484 })
529 })
485 queries.append(
530 queries.append(
486 {
531 {
487 'id': -2,
532 'id': -2,
488 'value': query,
533 'value': query,
489 'value_display': u'Commit search for: `{}`'.format(query),
534 'value_display': u'Commit search for: `{}`'.format(query),
490 'type': 'search',
535 'type': 'search',
491 'url': h.route_path('search',
536 'url': h.route_path('search',
492 _query={'q': query, 'type': 'commit'})
537 _query={'q': query, 'type': 'commit'})
493 })
538 })
494
539
495 return queries
540 return queries
496
541
497 @LoginRequired()
542 @LoginRequired()
498 @view_config(
543 @view_config(
499 route_name='goto_switcher_data', request_method='GET',
544 route_name='goto_switcher_data', request_method='GET',
500 renderer='json_ext', xhr=True)
545 renderer='json_ext', xhr=True)
501 def goto_switcher_data(self):
546 def goto_switcher_data(self):
502 c = self.load_default_context()
547 c = self.load_default_context()
503
548
504 _ = self.request.translate
549 _ = self.request.translate
505
550
506 query = self.request.GET.get('query')
551 query = self.request.GET.get('query')
507 log.debug('generating main filter data, query %s', query)
552 log.debug('generating main filter data, query %s', query)
508
553
509 res = []
554 res = []
510 if not query:
555 if not query:
511 return {'suggestions': res}
556 return {'suggestions': res}
512
557
558 def no_match(name):
559 return {
560 'id': -1,
561 'value': "",
562 'value_display': name,
563 'type': 'text',
564 'url': ""
565 }
513 searcher = searcher_from_config(self.request.registry.settings)
566 searcher = searcher_from_config(self.request.registry.settings)
514 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
567 has_specialized_search = False
515 res.append(_q)
516
568
569 # set repo context
570 repo = None
571 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
572 if repo_id:
573 repo = Repository.get(repo_id)
574
575 # set group context
576 repo_group = None
517 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]'))
518 if repo_group_id:
578 if repo_group_id:
519 repo_group = RepoGroup.get(repo_group_id)
579 repo_group = RepoGroup.get(repo_group_id)
520 composed_hint = '{}/{}'.format(repo_group.group_name, query)
580 prefix_match = False
521 show_hint = not query.startswith(repo_group.group_name)
522 if repo_group and show_hint:
523 hint = u'Repository search inside: `{}`'.format(composed_hint)
524 res.append({
525 'id': -1,
526 'value': composed_hint,
527 'value_display': hint,
528 'type': 'hint',
529 'url': ""
530 })
531
581
532 repo_groups = self._get_repo_group_list(query)
582 # user: type search
533 for serialized_repo_group in repo_groups:
583 if not prefix_match:
534 res.append(serialized_repo_group)
584 users, prefix_match = self._get_user_list(query)
535
585 if users:
536 repos = self._get_repo_list(query)
586 has_specialized_search = True
537 for serialized_repo in repos:
538 res.append(serialized_repo)
539
540 # TODO(marcink): should all logged in users be allowed to search others?
541 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
542 if allowed_user_search:
543 users = self._get_user_list(query)
544 for serialized_user in users:
587 for serialized_user in users:
545 res.append(serialized_user)
588 res.append(serialized_user)
589 elif prefix_match:
590 has_specialized_search = True
591 res.append(no_match('No matching users found'))
546
592
547 user_groups = self._get_user_groups_list(query)
593 # user_group: type search
594 if not prefix_match:
595 user_groups, prefix_match = self._get_user_groups_list(query)
596 if user_groups:
597 has_specialized_search = True
548 for serialized_user_group in user_groups:
598 for serialized_user_group in user_groups:
549 res.append(serialized_user_group)
599 res.append(serialized_user_group)
600 elif prefix_match:
601 has_specialized_search = True
602 res.append(no_match('No matching user groups found'))
550
603
551 commits = self._get_hash_commit_list(c.auth_user, searcher, query)
604 # FTS commit: type search
605 if not prefix_match:
606 commits, prefix_match = self._get_hash_commit_list(
607 c.auth_user, searcher, query, repo, repo_group)
552 if commits:
608 if commits:
609 has_specialized_search = True
553 unique_repos = collections.OrderedDict()
610 unique_repos = collections.OrderedDict()
554 for commit in commits:
611 for commit in commits:
555 repo_name = commit['repo']
612 repo_name = commit['repo']
556 unique_repos.setdefault(repo_name, []).append(commit)
613 unique_repos.setdefault(repo_name, []).append(commit)
557
614
558 for repo, commits in unique_repos.items():
615 for _repo, commits in unique_repos.items():
559 for commit in commits:
616 for commit in commits:
560 res.append(commit)
617 res.append(commit)
618 elif prefix_match:
619 has_specialized_search = True
620 res.append(no_match('No matching commits found'))
561
621
562 paths = self._get_path_list(c.auth_user, searcher, query)
622 # FTS file: type search
623 if not prefix_match:
624 paths, prefix_match = self._get_path_list(
625 c.auth_user, searcher, query, repo, repo_group)
563 if paths:
626 if paths:
627 has_specialized_search = True
564 unique_repos = collections.OrderedDict()
628 unique_repos = collections.OrderedDict()
565 for path in paths:
629 for path in paths:
566 repo_name = path['repo']
630 repo_name = path['repo']
567 unique_repos.setdefault(repo_name, []).append(path)
631 unique_repos.setdefault(repo_name, []).append(path)
568
632
569 for repo, paths in unique_repos.items():
633 for repo, paths in unique_repos.items():
570 for path in paths:
634 for path in paths:
571 res.append(path)
635 res.append(path)
636 elif prefix_match:
637 has_specialized_search = True
638 res.append(no_match('No matching files found'))
639
640 # main suggestions
641 if not has_specialized_search:
642 repo_group_name = ''
643 if repo_group:
644 repo_group_name = repo_group.group_name
645
646 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
647 res.append(_q)
648
649 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
650 for serialized_repo_group in repo_groups:
651 res.append(serialized_repo_group)
652
653 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
654 for serialized_repo in repos:
655 res.append(serialized_repo)
656
657 if not repos and not repo_groups:
658 res.append(no_match('No matches found'))
572
659
573 return {'suggestions': res}
660 return {'suggestions': res}
574
661
575 def _get_groups_and_repos(self, repo_group_id=None):
662 def _get_groups_and_repos(self, repo_group_id=None):
576 # repo groups groups
663 # repo groups groups
577 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)
578 _perms = ['group.read', 'group.write', 'group.admin']
665 _perms = ['group.read', 'group.write', 'group.admin']
579 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
666 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
580 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
667 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
581 repo_group_list=repo_group_list_acl, admin=False)
668 repo_group_list=repo_group_list_acl, admin=False)
582
669
583 # repositories
670 # repositories
584 repo_list = Repository.get_all_repos(group_id=repo_group_id)
671 repo_list = Repository.get_all_repos(group_id=repo_group_id)
585 _perms = ['repository.read', 'repository.write', 'repository.admin']
672 _perms = ['repository.read', 'repository.write', 'repository.admin']
586 repo_list_acl = RepoList(repo_list, perm_set=_perms)
673 repo_list_acl = RepoList(repo_list, perm_set=_perms)
587 repo_data = RepoModel().get_repos_as_dict(
674 repo_data = RepoModel().get_repos_as_dict(
588 repo_list=repo_list_acl, admin=False)
675 repo_list=repo_list_acl, admin=False)
589
676
590 return repo_data, repo_group_data
677 return repo_data, repo_group_data
591
678
592 @LoginRequired()
679 @LoginRequired()
593 @view_config(
680 @view_config(
594 route_name='home', request_method='GET',
681 route_name='home', request_method='GET',
595 renderer='rhodecode:templates/index.mako')
682 renderer='rhodecode:templates/index.mako')
596 def main_page(self):
683 def main_page(self):
597 c = self.load_default_context()
684 c = self.load_default_context()
598 c.repo_group = None
685 c.repo_group = None
599
686
600 repo_data, repo_group_data = self._get_groups_and_repos()
687 repo_data, repo_group_data = self._get_groups_and_repos()
601 # json used to render the grids
688 # json used to render the grids
602 c.repos_data = json.dumps(repo_data)
689 c.repos_data = json.dumps(repo_data)
603 c.repo_groups_data = json.dumps(repo_group_data)
690 c.repo_groups_data = json.dumps(repo_group_data)
604
691
605 return self._get_template_context(c)
692 return self._get_template_context(c)
606
693
607 @LoginRequired()
694 @LoginRequired()
608 @HasRepoGroupPermissionAnyDecorator(
695 @HasRepoGroupPermissionAnyDecorator(
609 'group.read', 'group.write', 'group.admin')
696 'group.read', 'group.write', 'group.admin')
610 @view_config(
697 @view_config(
611 route_name='repo_group_home', request_method='GET',
698 route_name='repo_group_home', request_method='GET',
612 renderer='rhodecode:templates/index_repo_group.mako')
699 renderer='rhodecode:templates/index_repo_group.mako')
613 @view_config(
700 @view_config(
614 route_name='repo_group_home_slash', request_method='GET',
701 route_name='repo_group_home_slash', request_method='GET',
615 renderer='rhodecode:templates/index_repo_group.mako')
702 renderer='rhodecode:templates/index_repo_group.mako')
616 def repo_group_main_page(self):
703 def repo_group_main_page(self):
617 c = self.load_default_context()
704 c = self.load_default_context()
618 c.repo_group = self.request.db_repo_group
705 c.repo_group = self.request.db_repo_group
619 repo_data, repo_group_data = self._get_groups_and_repos(
706 repo_data, repo_group_data = self._get_groups_and_repos(
620 c.repo_group.group_id)
707 c.repo_group.group_id)
621
708
622 # json used to render the grids
709 # json used to render the grids
623 c.repos_data = json.dumps(repo_data)
710 c.repos_data = json.dumps(repo_data)
624 c.repo_groups_data = json.dumps(repo_group_data)
711 c.repo_groups_data = json.dumps(repo_group_data)
625
712
626 return self._get_template_context(c)
713 return self._get_template_context(c)
627
714
628 @LoginRequired()
715 @LoginRequired()
629 @CSRFRequired()
716 @CSRFRequired()
630 @view_config(
717 @view_config(
631 route_name='markup_preview', request_method='POST',
718 route_name='markup_preview', request_method='POST',
632 renderer='string', xhr=True)
719 renderer='string', xhr=True)
633 def markup_preview(self):
720 def markup_preview(self):
634 # Technically a CSRF token is not needed as no state changes with this
721 # Technically a CSRF token is not needed as no state changes with this
635 # call. However, as this is a POST is better to have it, so automated
722 # call. However, as this is a POST is better to have it, so automated
636 # tools don't flag it as potential CSRF.
723 # tools don't flag it as potential CSRF.
637 # Post is required because the payload could be bigger than the maximum
724 # Post is required because the payload could be bigger than the maximum
638 # allowed by GET.
725 # allowed by GET.
639
726
640 text = self.request.POST.get('text')
727 text = self.request.POST.get('text')
641 renderer = self.request.POST.get('renderer') or 'rst'
728 renderer = self.request.POST.get('renderer') or 'rst'
642 if text:
729 if text:
643 return h.render(text, renderer=renderer, mentions=True)
730 return h.render(text, renderer=renderer, mentions=True)
644 return ''
731 return ''
645
732
646 @LoginRequired()
733 @LoginRequired()
647 @CSRFRequired()
734 @CSRFRequired()
648 @view_config(
735 @view_config(
649 route_name='store_user_session_value', request_method='POST',
736 route_name='store_user_session_value', request_method='POST',
650 renderer='string', xhr=True)
737 renderer='string', xhr=True)
651 def store_user_session_attr(self):
738 def store_user_session_attr(self):
652 key = self.request.POST.get('key')
739 key = self.request.POST.get('key')
653 val = self.request.POST.get('val')
740 val = self.request.POST.get('val')
654
741
655 existing_value = self.request.session.get(key)
742 existing_value = self.request.session.get(key)
656 if existing_value != val:
743 if existing_value != val:
657 self.request.session[key] = val
744 self.request.session[key] = val
658
745
659 return 'stored:{}'.format(key)
746 return 'stored:{}'.format(key)
@@ -1,2042 +1,2042 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 tooltip = _ToolTip()
217 tooltip = _ToolTip()
218
218
219
219
220 def files_breadcrumbs(repo_name, commit_id, file_path):
220 def files_breadcrumbs(repo_name, commit_id, file_path):
221 if isinstance(file_path, str):
221 if isinstance(file_path, str):
222 file_path = safe_unicode(file_path)
222 file_path = safe_unicode(file_path)
223
223
224 # TODO: johbo: Is this always a url like path, or is this operating
224 # TODO: johbo: Is this always a url like path, or is this operating
225 # system dependent?
225 # system dependent?
226 path_segments = file_path.split('/')
226 path_segments = file_path.split('/')
227
227
228 repo_name_html = escape(repo_name)
228 repo_name_html = escape(repo_name)
229 if len(path_segments) == 1 and path_segments[0] == '':
229 if len(path_segments) == 1 and path_segments[0] == '':
230 url_segments = [repo_name_html]
230 url_segments = [repo_name_html]
231 else:
231 else:
232 url_segments = [
232 url_segments = [
233 link_to(
233 link_to(
234 repo_name_html,
234 repo_name_html,
235 route_path(
235 route_path(
236 'repo_files',
236 'repo_files',
237 repo_name=repo_name,
237 repo_name=repo_name,
238 commit_id=commit_id,
238 commit_id=commit_id,
239 f_path=''),
239 f_path=''),
240 class_='pjax-link')]
240 class_='pjax-link')]
241
241
242 last_cnt = len(path_segments) - 1
242 last_cnt = len(path_segments) - 1
243 for cnt, segment in enumerate(path_segments):
243 for cnt, segment in enumerate(path_segments):
244 if not segment:
244 if not segment:
245 continue
245 continue
246 segment_html = escape(segment)
246 segment_html = escape(segment)
247
247
248 if cnt != last_cnt:
248 if cnt != last_cnt:
249 url_segments.append(
249 url_segments.append(
250 link_to(
250 link_to(
251 segment_html,
251 segment_html,
252 route_path(
252 route_path(
253 'repo_files',
253 'repo_files',
254 repo_name=repo_name,
254 repo_name=repo_name,
255 commit_id=commit_id,
255 commit_id=commit_id,
256 f_path='/'.join(path_segments[:cnt + 1])),
256 f_path='/'.join(path_segments[:cnt + 1])),
257 class_='pjax-link'))
257 class_='pjax-link'))
258 else:
258 else:
259 url_segments.append(segment_html)
259 url_segments.append(segment_html)
260
260
261 return literal('/'.join(url_segments))
261 return literal('/'.join(url_segments))
262
262
263
263
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
265 """
265 """
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
267
267
268 If ``outfile`` is given and a valid file object (an object
268 If ``outfile`` is given and a valid file object (an object
269 with a ``write`` method), the result will be written to it, otherwise
269 with a ``write`` method), the result will be written to it, otherwise
270 it is returned as a string.
270 it is returned as a string.
271 """
271 """
272 if use_hl_filter:
272 if use_hl_filter:
273 # add HL filter
273 # add HL filter
274 from rhodecode.lib.index import search_utils
274 from rhodecode.lib.index import search_utils
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
276 return pygments.format(pygments.lex(code, lexer), formatter)
276 return pygments.format(pygments.lex(code, lexer), formatter)
277
277
278
278
279 class CodeHtmlFormatter(HtmlFormatter):
279 class CodeHtmlFormatter(HtmlFormatter):
280 """
280 """
281 My code Html Formatter for source codes
281 My code Html Formatter for source codes
282 """
282 """
283
283
284 def wrap(self, source, outfile):
284 def wrap(self, source, outfile):
285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
286
286
287 def _wrap_code(self, source):
287 def _wrap_code(self, source):
288 for cnt, it in enumerate(source):
288 for cnt, it in enumerate(source):
289 i, t = it
289 i, t = it
290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
291 yield i, t
291 yield i, t
292
292
293 def _wrap_tablelinenos(self, inner):
293 def _wrap_tablelinenos(self, inner):
294 dummyoutfile = StringIO.StringIO()
294 dummyoutfile = StringIO.StringIO()
295 lncount = 0
295 lncount = 0
296 for t, line in inner:
296 for t, line in inner:
297 if t:
297 if t:
298 lncount += 1
298 lncount += 1
299 dummyoutfile.write(line)
299 dummyoutfile.write(line)
300
300
301 fl = self.linenostart
301 fl = self.linenostart
302 mw = len(str(lncount + fl - 1))
302 mw = len(str(lncount + fl - 1))
303 sp = self.linenospecial
303 sp = self.linenospecial
304 st = self.linenostep
304 st = self.linenostep
305 la = self.lineanchors
305 la = self.lineanchors
306 aln = self.anchorlinenos
306 aln = self.anchorlinenos
307 nocls = self.noclasses
307 nocls = self.noclasses
308 if sp:
308 if sp:
309 lines = []
309 lines = []
310
310
311 for i in range(fl, fl + lncount):
311 for i in range(fl, fl + lncount):
312 if i % st == 0:
312 if i % st == 0:
313 if i % sp == 0:
313 if i % sp == 0:
314 if aln:
314 if aln:
315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
316 (la, i, mw, i))
316 (la, i, mw, i))
317 else:
317 else:
318 lines.append('<span class="special">%*d</span>' % (mw, i))
318 lines.append('<span class="special">%*d</span>' % (mw, i))
319 else:
319 else:
320 if aln:
320 if aln:
321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
322 else:
322 else:
323 lines.append('%*d' % (mw, i))
323 lines.append('%*d' % (mw, i))
324 else:
324 else:
325 lines.append('')
325 lines.append('')
326 ls = '\n'.join(lines)
326 ls = '\n'.join(lines)
327 else:
327 else:
328 lines = []
328 lines = []
329 for i in range(fl, fl + lncount):
329 for i in range(fl, fl + lncount):
330 if i % st == 0:
330 if i % st == 0:
331 if aln:
331 if aln:
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
333 else:
333 else:
334 lines.append('%*d' % (mw, i))
334 lines.append('%*d' % (mw, i))
335 else:
335 else:
336 lines.append('')
336 lines.append('')
337 ls = '\n'.join(lines)
337 ls = '\n'.join(lines)
338
338
339 # in case you wonder about the seemingly redundant <div> here: since the
339 # in case you wonder about the seemingly redundant <div> here: since the
340 # content in the other cell also is wrapped in a div, some browsers in
340 # content in the other cell also is wrapped in a div, some browsers in
341 # some configurations seem to mess up the formatting...
341 # some configurations seem to mess up the formatting...
342 if nocls:
342 if nocls:
343 yield 0, ('<table class="%stable">' % self.cssclass +
343 yield 0, ('<table class="%stable">' % self.cssclass +
344 '<tr><td><div class="linenodiv" '
344 '<tr><td><div class="linenodiv" '
345 'style="background-color: #f0f0f0; padding-right: 10px">'
345 'style="background-color: #f0f0f0; padding-right: 10px">'
346 '<pre style="line-height: 125%">' +
346 '<pre style="line-height: 125%">' +
347 ls + '</pre></div></td><td id="hlcode" class="code">')
347 ls + '</pre></div></td><td id="hlcode" class="code">')
348 else:
348 else:
349 yield 0, ('<table class="%stable">' % self.cssclass +
349 yield 0, ('<table class="%stable">' % self.cssclass +
350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
351 ls + '</pre></div></td><td id="hlcode" class="code">')
351 ls + '</pre></div></td><td id="hlcode" class="code">')
352 yield 0, dummyoutfile.getvalue()
352 yield 0, dummyoutfile.getvalue()
353 yield 0, '</td></tr></table>'
353 yield 0, '</td></tr></table>'
354
354
355
355
356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
357 def __init__(self, **kw):
357 def __init__(self, **kw):
358 # only show these line numbers if set
358 # only show these line numbers if set
359 self.only_lines = kw.pop('only_line_numbers', [])
359 self.only_lines = kw.pop('only_line_numbers', [])
360 self.query_terms = kw.pop('query_terms', [])
360 self.query_terms = kw.pop('query_terms', [])
361 self.max_lines = kw.pop('max_lines', 5)
361 self.max_lines = kw.pop('max_lines', 5)
362 self.line_context = kw.pop('line_context', 3)
362 self.line_context = kw.pop('line_context', 3)
363 self.url = kw.pop('url', None)
363 self.url = kw.pop('url', None)
364
364
365 super(CodeHtmlFormatter, self).__init__(**kw)
365 super(CodeHtmlFormatter, self).__init__(**kw)
366
366
367 def _wrap_code(self, source):
367 def _wrap_code(self, source):
368 for cnt, it in enumerate(source):
368 for cnt, it in enumerate(source):
369 i, t = it
369 i, t = it
370 t = '<pre>%s</pre>' % t
370 t = '<pre>%s</pre>' % t
371 yield i, t
371 yield i, t
372
372
373 def _wrap_tablelinenos(self, inner):
373 def _wrap_tablelinenos(self, inner):
374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
375
375
376 last_shown_line_number = 0
376 last_shown_line_number = 0
377 current_line_number = 1
377 current_line_number = 1
378
378
379 for t, line in inner:
379 for t, line in inner:
380 if not t:
380 if not t:
381 yield t, line
381 yield t, line
382 continue
382 continue
383
383
384 if current_line_number in self.only_lines:
384 if current_line_number in self.only_lines:
385 if last_shown_line_number + 1 != current_line_number:
385 if last_shown_line_number + 1 != current_line_number:
386 yield 0, '<tr>'
386 yield 0, '<tr>'
387 yield 0, '<td class="line">...</td>'
387 yield 0, '<td class="line">...</td>'
388 yield 0, '<td id="hlcode" class="code"></td>'
388 yield 0, '<td id="hlcode" class="code"></td>'
389 yield 0, '</tr>'
389 yield 0, '</tr>'
390
390
391 yield 0, '<tr>'
391 yield 0, '<tr>'
392 if self.url:
392 if self.url:
393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
394 self.url, current_line_number, current_line_number)
394 self.url, current_line_number, current_line_number)
395 else:
395 else:
396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
397 current_line_number)
397 current_line_number)
398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
399 yield 0, '</tr>'
399 yield 0, '</tr>'
400
400
401 last_shown_line_number = current_line_number
401 last_shown_line_number = current_line_number
402
402
403 current_line_number += 1
403 current_line_number += 1
404
404
405 yield 0, '</table>'
405 yield 0, '</table>'
406
406
407
407
408 def hsv_to_rgb(h, s, v):
408 def hsv_to_rgb(h, s, v):
409 """ Convert hsv color values to rgb """
409 """ Convert hsv color values to rgb """
410
410
411 if s == 0.0:
411 if s == 0.0:
412 return v, v, v
412 return v, v, v
413 i = int(h * 6.0) # XXX assume int() truncates!
413 i = int(h * 6.0) # XXX assume int() truncates!
414 f = (h * 6.0) - i
414 f = (h * 6.0) - i
415 p = v * (1.0 - s)
415 p = v * (1.0 - s)
416 q = v * (1.0 - s * f)
416 q = v * (1.0 - s * f)
417 t = v * (1.0 - s * (1.0 - f))
417 t = v * (1.0 - s * (1.0 - f))
418 i = i % 6
418 i = i % 6
419 if i == 0:
419 if i == 0:
420 return v, t, p
420 return v, t, p
421 if i == 1:
421 if i == 1:
422 return q, v, p
422 return q, v, p
423 if i == 2:
423 if i == 2:
424 return p, v, t
424 return p, v, t
425 if i == 3:
425 if i == 3:
426 return p, q, v
426 return p, q, v
427 if i == 4:
427 if i == 4:
428 return t, p, v
428 return t, p, v
429 if i == 5:
429 if i == 5:
430 return v, p, q
430 return v, p, q
431
431
432
432
433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
434 """
434 """
435 Generator for getting n of evenly distributed colors using
435 Generator for getting n of evenly distributed colors using
436 hsv color and golden ratio. It always return same order of colors
436 hsv color and golden ratio. It always return same order of colors
437
437
438 :param n: number of colors to generate
438 :param n: number of colors to generate
439 :param saturation: saturation of returned colors
439 :param saturation: saturation of returned colors
440 :param lightness: lightness of returned colors
440 :param lightness: lightness of returned colors
441 :returns: RGB tuple
441 :returns: RGB tuple
442 """
442 """
443
443
444 golden_ratio = 0.618033988749895
444 golden_ratio = 0.618033988749895
445 h = 0.22717784590367374
445 h = 0.22717784590367374
446
446
447 for _ in xrange(n):
447 for _ in xrange(n):
448 h += golden_ratio
448 h += golden_ratio
449 h %= 1
449 h %= 1
450 HSV_tuple = [h, saturation, lightness]
450 HSV_tuple = [h, saturation, lightness]
451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
453
453
454
454
455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
456 """
456 """
457 Returns a function which when called with an argument returns a unique
457 Returns a function which when called with an argument returns a unique
458 color for that argument, eg.
458 color for that argument, eg.
459
459
460 :param n: number of colors to generate
460 :param n: number of colors to generate
461 :param saturation: saturation of returned colors
461 :param saturation: saturation of returned colors
462 :param lightness: lightness of returned colors
462 :param lightness: lightness of returned colors
463 :returns: css RGB string
463 :returns: css RGB string
464
464
465 >>> color_hash = color_hasher()
465 >>> color_hash = color_hasher()
466 >>> color_hash('hello')
466 >>> color_hash('hello')
467 'rgb(34, 12, 59)'
467 'rgb(34, 12, 59)'
468 >>> color_hash('hello')
468 >>> color_hash('hello')
469 'rgb(34, 12, 59)'
469 'rgb(34, 12, 59)'
470 >>> color_hash('other')
470 >>> color_hash('other')
471 'rgb(90, 224, 159)'
471 'rgb(90, 224, 159)'
472 """
472 """
473
473
474 color_dict = {}
474 color_dict = {}
475 cgenerator = unique_color_generator(
475 cgenerator = unique_color_generator(
476 saturation=saturation, lightness=lightness)
476 saturation=saturation, lightness=lightness)
477
477
478 def get_color_string(thing):
478 def get_color_string(thing):
479 if thing in color_dict:
479 if thing in color_dict:
480 col = color_dict[thing]
480 col = color_dict[thing]
481 else:
481 else:
482 col = color_dict[thing] = cgenerator.next()
482 col = color_dict[thing] = cgenerator.next()
483 return "rgb(%s)" % (', '.join(col))
483 return "rgb(%s)" % (', '.join(col))
484
484
485 return get_color_string
485 return get_color_string
486
486
487
487
488 def get_lexer_safe(mimetype=None, filepath=None):
488 def get_lexer_safe(mimetype=None, filepath=None):
489 """
489 """
490 Tries to return a relevant pygments lexer using mimetype/filepath name,
490 Tries to return a relevant pygments lexer using mimetype/filepath name,
491 defaulting to plain text if none could be found
491 defaulting to plain text if none could be found
492 """
492 """
493 lexer = None
493 lexer = None
494 try:
494 try:
495 if mimetype:
495 if mimetype:
496 lexer = get_lexer_for_mimetype(mimetype)
496 lexer = get_lexer_for_mimetype(mimetype)
497 if not lexer:
497 if not lexer:
498 lexer = get_lexer_for_filename(filepath)
498 lexer = get_lexer_for_filename(filepath)
499 except pygments.util.ClassNotFound:
499 except pygments.util.ClassNotFound:
500 pass
500 pass
501
501
502 if not lexer:
502 if not lexer:
503 lexer = get_lexer_by_name('text')
503 lexer = get_lexer_by_name('text')
504
504
505 return lexer
505 return lexer
506
506
507
507
508 def get_lexer_for_filenode(filenode):
508 def get_lexer_for_filenode(filenode):
509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
510 return lexer
510 return lexer
511
511
512
512
513 def pygmentize(filenode, **kwargs):
513 def pygmentize(filenode, **kwargs):
514 """
514 """
515 pygmentize function using pygments
515 pygmentize function using pygments
516
516
517 :param filenode:
517 :param filenode:
518 """
518 """
519 lexer = get_lexer_for_filenode(filenode)
519 lexer = get_lexer_for_filenode(filenode)
520 return literal(code_highlight(filenode.content, lexer,
520 return literal(code_highlight(filenode.content, lexer,
521 CodeHtmlFormatter(**kwargs)))
521 CodeHtmlFormatter(**kwargs)))
522
522
523
523
524 def is_following_repo(repo_name, user_id):
524 def is_following_repo(repo_name, user_id):
525 from rhodecode.model.scm import ScmModel
525 from rhodecode.model.scm import ScmModel
526 return ScmModel().is_following_repo(repo_name, user_id)
526 return ScmModel().is_following_repo(repo_name, user_id)
527
527
528
528
529 class _Message(object):
529 class _Message(object):
530 """A message returned by ``Flash.pop_messages()``.
530 """A message returned by ``Flash.pop_messages()``.
531
531
532 Converting the message to a string returns the message text. Instances
532 Converting the message to a string returns the message text. Instances
533 also have the following attributes:
533 also have the following attributes:
534
534
535 * ``message``: the message text.
535 * ``message``: the message text.
536 * ``category``: the category specified when the message was created.
536 * ``category``: the category specified when the message was created.
537 """
537 """
538
538
539 def __init__(self, category, message):
539 def __init__(self, category, message):
540 self.category = category
540 self.category = category
541 self.message = message
541 self.message = message
542
542
543 def __str__(self):
543 def __str__(self):
544 return self.message
544 return self.message
545
545
546 __unicode__ = __str__
546 __unicode__ = __str__
547
547
548 def __html__(self):
548 def __html__(self):
549 return escape(safe_unicode(self.message))
549 return escape(safe_unicode(self.message))
550
550
551
551
552 class Flash(object):
552 class Flash(object):
553 # List of allowed categories. If None, allow any category.
553 # List of allowed categories. If None, allow any category.
554 categories = ["warning", "notice", "error", "success"]
554 categories = ["warning", "notice", "error", "success"]
555
555
556 # Default category if none is specified.
556 # Default category if none is specified.
557 default_category = "notice"
557 default_category = "notice"
558
558
559 def __init__(self, session_key="flash", categories=None,
559 def __init__(self, session_key="flash", categories=None,
560 default_category=None):
560 default_category=None):
561 """
561 """
562 Instantiate a ``Flash`` object.
562 Instantiate a ``Flash`` object.
563
563
564 ``session_key`` is the key to save the messages under in the user's
564 ``session_key`` is the key to save the messages under in the user's
565 session.
565 session.
566
566
567 ``categories`` is an optional list which overrides the default list
567 ``categories`` is an optional list which overrides the default list
568 of categories.
568 of categories.
569
569
570 ``default_category`` overrides the default category used for messages
570 ``default_category`` overrides the default category used for messages
571 when none is specified.
571 when none is specified.
572 """
572 """
573 self.session_key = session_key
573 self.session_key = session_key
574 if categories is not None:
574 if categories is not None:
575 self.categories = categories
575 self.categories = categories
576 if default_category is not None:
576 if default_category is not None:
577 self.default_category = default_category
577 self.default_category = default_category
578 if self.categories and self.default_category not in self.categories:
578 if self.categories and self.default_category not in self.categories:
579 raise ValueError(
579 raise ValueError(
580 "unrecognized default category %r" % (self.default_category,))
580 "unrecognized default category %r" % (self.default_category,))
581
581
582 def pop_messages(self, session=None, request=None):
582 def pop_messages(self, session=None, request=None):
583 """
583 """
584 Return all accumulated messages and delete them from the session.
584 Return all accumulated messages and delete them from the session.
585
585
586 The return value is a list of ``Message`` objects.
586 The return value is a list of ``Message`` objects.
587 """
587 """
588 messages = []
588 messages = []
589
589
590 if not session:
590 if not session:
591 if not request:
591 if not request:
592 request = get_current_request()
592 request = get_current_request()
593 session = request.session
593 session = request.session
594
594
595 # Pop the 'old' pylons flash messages. They are tuples of the form
595 # Pop the 'old' pylons flash messages. They are tuples of the form
596 # (category, message)
596 # (category, message)
597 for cat, msg in session.pop(self.session_key, []):
597 for cat, msg in session.pop(self.session_key, []):
598 messages.append(_Message(cat, msg))
598 messages.append(_Message(cat, msg))
599
599
600 # Pop the 'new' pyramid flash messages for each category as list
600 # Pop the 'new' pyramid flash messages for each category as list
601 # of strings.
601 # of strings.
602 for cat in self.categories:
602 for cat in self.categories:
603 for msg in session.pop_flash(queue=cat):
603 for msg in session.pop_flash(queue=cat):
604 messages.append(_Message(cat, msg))
604 messages.append(_Message(cat, msg))
605 # Map messages from the default queue to the 'notice' category.
605 # Map messages from the default queue to the 'notice' category.
606 for msg in session.pop_flash():
606 for msg in session.pop_flash():
607 messages.append(_Message('notice', msg))
607 messages.append(_Message('notice', msg))
608
608
609 session.save()
609 session.save()
610 return messages
610 return messages
611
611
612 def json_alerts(self, session=None, request=None):
612 def json_alerts(self, session=None, request=None):
613 payloads = []
613 payloads = []
614 messages = flash.pop_messages(session=session, request=request)
614 messages = flash.pop_messages(session=session, request=request)
615 if messages:
615 if messages:
616 for message in messages:
616 for message in messages:
617 subdata = {}
617 subdata = {}
618 if hasattr(message.message, 'rsplit'):
618 if hasattr(message.message, 'rsplit'):
619 flash_data = message.message.rsplit('|DELIM|', 1)
619 flash_data = message.message.rsplit('|DELIM|', 1)
620 org_message = flash_data[0]
620 org_message = flash_data[0]
621 if len(flash_data) > 1:
621 if len(flash_data) > 1:
622 subdata = json.loads(flash_data[1])
622 subdata = json.loads(flash_data[1])
623 else:
623 else:
624 org_message = message.message
624 org_message = message.message
625 payloads.append({
625 payloads.append({
626 'message': {
626 'message': {
627 'message': u'{}'.format(org_message),
627 'message': u'{}'.format(org_message),
628 'level': message.category,
628 'level': message.category,
629 'force': True,
629 'force': True,
630 'subdata': subdata
630 'subdata': subdata
631 }
631 }
632 })
632 })
633 return json.dumps(payloads)
633 return json.dumps(payloads)
634
634
635 def __call__(self, message, category=None, ignore_duplicate=False,
635 def __call__(self, message, category=None, ignore_duplicate=False,
636 session=None, request=None):
636 session=None, request=None):
637
637
638 if not session:
638 if not session:
639 if not request:
639 if not request:
640 request = get_current_request()
640 request = get_current_request()
641 session = request.session
641 session = request.session
642
642
643 session.flash(
643 session.flash(
644 message, queue=category, allow_duplicate=not ignore_duplicate)
644 message, queue=category, allow_duplicate=not ignore_duplicate)
645
645
646
646
647 flash = Flash()
647 flash = Flash()
648
648
649 #==============================================================================
649 #==============================================================================
650 # SCM FILTERS available via h.
650 # SCM FILTERS available via h.
651 #==============================================================================
651 #==============================================================================
652 from rhodecode.lib.vcs.utils import author_name, author_email
652 from rhodecode.lib.vcs.utils import author_name, author_email
653 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
653 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
654 from rhodecode.model.db import User, ChangesetStatus
654 from rhodecode.model.db import User, ChangesetStatus
655
655
656 capitalize = lambda x: x.capitalize()
656 capitalize = lambda x: x.capitalize()
657 email = author_email
657 email = author_email
658 short_id = lambda x: x[:12]
658 short_id = lambda x: x[:12]
659 hide_credentials = lambda x: ''.join(credentials_filter(x))
659 hide_credentials = lambda x: ''.join(credentials_filter(x))
660
660
661
661
662 import pytz
662 import pytz
663 import tzlocal
663 import tzlocal
664 local_timezone = tzlocal.get_localzone()
664 local_timezone = tzlocal.get_localzone()
665
665
666
666
667 def age_component(datetime_iso, value=None, time_is_local=False):
667 def age_component(datetime_iso, value=None, time_is_local=False):
668 title = value or format_date(datetime_iso)
668 title = value or format_date(datetime_iso)
669 tzinfo = '+00:00'
669 tzinfo = '+00:00'
670
670
671 # detect if we have a timezone info, otherwise, add it
671 # detect if we have a timezone info, otherwise, add it
672 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
672 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
673 force_timezone = os.environ.get('RC_TIMEZONE', '')
673 force_timezone = os.environ.get('RC_TIMEZONE', '')
674 if force_timezone:
674 if force_timezone:
675 force_timezone = pytz.timezone(force_timezone)
675 force_timezone = pytz.timezone(force_timezone)
676 timezone = force_timezone or local_timezone
676 timezone = force_timezone or local_timezone
677 offset = timezone.localize(datetime_iso).strftime('%z')
677 offset = timezone.localize(datetime_iso).strftime('%z')
678 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
678 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
679
679
680 return literal(
680 return literal(
681 '<time class="timeago tooltip" '
681 '<time class="timeago tooltip" '
682 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
682 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
683 datetime_iso, title, tzinfo))
683 datetime_iso, title, tzinfo))
684
684
685
685
686 def _shorten_commit_id(commit_id, commit_len=None):
686 def _shorten_commit_id(commit_id, commit_len=None):
687 if commit_len is None:
687 if commit_len is None:
688 request = get_current_request()
688 request = get_current_request()
689 commit_len = request.call_context.visual.show_sha_length
689 commit_len = request.call_context.visual.show_sha_length
690 return commit_id[:commit_len]
690 return commit_id[:commit_len]
691
691
692
692
693 def show_id(commit, show_idx=None, commit_len=None):
693 def show_id(commit, show_idx=None, commit_len=None):
694 """
694 """
695 Configurable function that shows ID
695 Configurable function that shows ID
696 by default it's r123:fffeeefffeee
696 by default it's r123:fffeeefffeee
697
697
698 :param commit: commit instance
698 :param commit: commit instance
699 """
699 """
700 if show_idx is None:
700 if show_idx is None:
701 request = get_current_request()
701 request = get_current_request()
702 show_idx = request.call_context.visual.show_revision_number
702 show_idx = request.call_context.visual.show_revision_number
703
703
704 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
704 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
705 if show_idx:
705 if show_idx:
706 return 'r%s:%s' % (commit.idx, raw_id)
706 return 'r%s:%s' % (commit.idx, raw_id)
707 else:
707 else:
708 return '%s' % (raw_id, )
708 return '%s' % (raw_id, )
709
709
710
710
711 def format_date(date):
711 def format_date(date):
712 """
712 """
713 use a standardized formatting for dates used in RhodeCode
713 use a standardized formatting for dates used in RhodeCode
714
714
715 :param date: date/datetime object
715 :param date: date/datetime object
716 :return: formatted date
716 :return: formatted date
717 """
717 """
718
718
719 if date:
719 if date:
720 _fmt = "%a, %d %b %Y %H:%M:%S"
720 _fmt = "%a, %d %b %Y %H:%M:%S"
721 return safe_unicode(date.strftime(_fmt))
721 return safe_unicode(date.strftime(_fmt))
722
722
723 return u""
723 return u""
724
724
725
725
726 class _RepoChecker(object):
726 class _RepoChecker(object):
727
727
728 def __init__(self, backend_alias):
728 def __init__(self, backend_alias):
729 self._backend_alias = backend_alias
729 self._backend_alias = backend_alias
730
730
731 def __call__(self, repository):
731 def __call__(self, repository):
732 if hasattr(repository, 'alias'):
732 if hasattr(repository, 'alias'):
733 _type = repository.alias
733 _type = repository.alias
734 elif hasattr(repository, 'repo_type'):
734 elif hasattr(repository, 'repo_type'):
735 _type = repository.repo_type
735 _type = repository.repo_type
736 else:
736 else:
737 _type = repository
737 _type = repository
738 return _type == self._backend_alias
738 return _type == self._backend_alias
739
739
740
740
741 is_git = _RepoChecker('git')
741 is_git = _RepoChecker('git')
742 is_hg = _RepoChecker('hg')
742 is_hg = _RepoChecker('hg')
743 is_svn = _RepoChecker('svn')
743 is_svn = _RepoChecker('svn')
744
744
745
745
746 def get_repo_type_by_name(repo_name):
746 def get_repo_type_by_name(repo_name):
747 repo = Repository.get_by_repo_name(repo_name)
747 repo = Repository.get_by_repo_name(repo_name)
748 if repo:
748 if repo:
749 return repo.repo_type
749 return repo.repo_type
750
750
751
751
752 def is_svn_without_proxy(repository):
752 def is_svn_without_proxy(repository):
753 if is_svn(repository):
753 if is_svn(repository):
754 from rhodecode.model.settings import VcsSettingsModel
754 from rhodecode.model.settings import VcsSettingsModel
755 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
755 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
756 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
756 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
757 return False
757 return False
758
758
759
759
760 def discover_user(author):
760 def discover_user(author):
761 """
761 """
762 Tries to discover RhodeCode User based on the autho string. Author string
762 Tries to discover RhodeCode User based on the autho string. Author string
763 is typically `FirstName LastName <email@address.com>`
763 is typically `FirstName LastName <email@address.com>`
764 """
764 """
765
765
766 # if author is already an instance use it for extraction
766 # if author is already an instance use it for extraction
767 if isinstance(author, User):
767 if isinstance(author, User):
768 return author
768 return author
769
769
770 # Valid email in the attribute passed, see if they're in the system
770 # Valid email in the attribute passed, see if they're in the system
771 _email = author_email(author)
771 _email = author_email(author)
772 if _email != '':
772 if _email != '':
773 user = User.get_by_email(_email, case_insensitive=True, cache=True)
773 user = User.get_by_email(_email, case_insensitive=True, cache=True)
774 if user is not None:
774 if user is not None:
775 return user
775 return user
776
776
777 # Maybe it's a username, we try to extract it and fetch by username ?
777 # Maybe it's a username, we try to extract it and fetch by username ?
778 _author = author_name(author)
778 _author = author_name(author)
779 user = User.get_by_username(_author, case_insensitive=True, cache=True)
779 user = User.get_by_username(_author, case_insensitive=True, cache=True)
780 if user is not None:
780 if user is not None:
781 return user
781 return user
782
782
783 return None
783 return None
784
784
785
785
786 def email_or_none(author):
786 def email_or_none(author):
787 # extract email from the commit string
787 # extract email from the commit string
788 _email = author_email(author)
788 _email = author_email(author)
789
789
790 # If we have an email, use it, otherwise
790 # If we have an email, use it, otherwise
791 # see if it contains a username we can get an email from
791 # see if it contains a username we can get an email from
792 if _email != '':
792 if _email != '':
793 return _email
793 return _email
794 else:
794 else:
795 user = User.get_by_username(
795 user = User.get_by_username(
796 author_name(author), case_insensitive=True, cache=True)
796 author_name(author), case_insensitive=True, cache=True)
797
797
798 if user is not None:
798 if user is not None:
799 return user.email
799 return user.email
800
800
801 # No valid email, not a valid user in the system, none!
801 # No valid email, not a valid user in the system, none!
802 return None
802 return None
803
803
804
804
805 def link_to_user(author, length=0, **kwargs):
805 def link_to_user(author, length=0, **kwargs):
806 user = discover_user(author)
806 user = discover_user(author)
807 # user can be None, but if we have it already it means we can re-use it
807 # user can be None, but if we have it already it means we can re-use it
808 # in the person() function, so we save 1 intensive-query
808 # in the person() function, so we save 1 intensive-query
809 if user:
809 if user:
810 author = user
810 author = user
811
811
812 display_person = person(author, 'username_or_name_or_email')
812 display_person = person(author, 'username_or_name_or_email')
813 if length:
813 if length:
814 display_person = shorter(display_person, length)
814 display_person = shorter(display_person, length)
815
815
816 if user:
816 if user:
817 return link_to(
817 return link_to(
818 escape(display_person),
818 escape(display_person),
819 route_path('user_profile', username=user.username),
819 route_path('user_profile', username=user.username),
820 **kwargs)
820 **kwargs)
821 else:
821 else:
822 return escape(display_person)
822 return escape(display_person)
823
823
824
824
825 def link_to_group(users_group_name, **kwargs):
825 def link_to_group(users_group_name, **kwargs):
826 return link_to(
826 return link_to(
827 escape(users_group_name),
827 escape(users_group_name),
828 route_path('user_group_profile', user_group_name=users_group_name),
828 route_path('user_group_profile', user_group_name=users_group_name),
829 **kwargs)
829 **kwargs)
830
830
831
831
832 def person(author, show_attr="username_and_name"):
832 def person(author, show_attr="username_and_name"):
833 user = discover_user(author)
833 user = discover_user(author)
834 if user:
834 if user:
835 return getattr(user, show_attr)
835 return getattr(user, show_attr)
836 else:
836 else:
837 _author = author_name(author)
837 _author = author_name(author)
838 _email = email(author)
838 _email = email(author)
839 return _author or _email
839 return _author or _email
840
840
841
841
842 def author_string(email):
842 def author_string(email):
843 if email:
843 if email:
844 user = User.get_by_email(email, case_insensitive=True, cache=True)
844 user = User.get_by_email(email, case_insensitive=True, cache=True)
845 if user:
845 if user:
846 if user.first_name or user.last_name:
846 if user.first_name or user.last_name:
847 return '%s %s &lt;%s&gt;' % (
847 return '%s %s &lt;%s&gt;' % (
848 user.first_name, user.last_name, email)
848 user.first_name, user.last_name, email)
849 else:
849 else:
850 return email
850 return email
851 else:
851 else:
852 return email
852 return email
853 else:
853 else:
854 return None
854 return None
855
855
856
856
857 def person_by_id(id_, show_attr="username_and_name"):
857 def person_by_id(id_, show_attr="username_and_name"):
858 # attr to return from fetched user
858 # attr to return from fetched user
859 person_getter = lambda usr: getattr(usr, show_attr)
859 person_getter = lambda usr: getattr(usr, show_attr)
860
860
861 #maybe it's an ID ?
861 #maybe it's an ID ?
862 if str(id_).isdigit() or isinstance(id_, int):
862 if str(id_).isdigit() or isinstance(id_, int):
863 id_ = int(id_)
863 id_ = int(id_)
864 user = User.get(id_)
864 user = User.get(id_)
865 if user is not None:
865 if user is not None:
866 return person_getter(user)
866 return person_getter(user)
867 return id_
867 return id_
868
868
869
869
870 def gravatar_with_user(request, author, show_disabled=False):
870 def gravatar_with_user(request, author, show_disabled=False):
871 _render = request.get_partial_renderer(
871 _render = request.get_partial_renderer(
872 'rhodecode:templates/base/base.mako')
872 'rhodecode:templates/base/base.mako')
873 return _render('gravatar_with_user', author, show_disabled=show_disabled)
873 return _render('gravatar_with_user', author, show_disabled=show_disabled)
874
874
875
875
876 tags_paterns = OrderedDict((
876 tags_paterns = OrderedDict((
877 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
877 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
878 '<div class="metatag" tag="lang">\\2</div>')),
878 '<div class="metatag" tag="lang">\\2</div>')),
879
879
880 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
880 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
881 '<div class="metatag" tag="see">see: \\1 </div>')),
881 '<div class="metatag" tag="see">see: \\1 </div>')),
882
882
883 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
883 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
884 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
884 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
885
885
886 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
886 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
887 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
887 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
888
888
889 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
889 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
890 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
890 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
891
891
892 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
892 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
893 '<div class="metatag" tag="state \\1">\\1</div>')),
893 '<div class="metatag" tag="state \\1">\\1</div>')),
894
894
895 # label in grey
895 # label in grey
896 ('label', (re.compile(r'\[([a-z]+)\]'),
896 ('label', (re.compile(r'\[([a-z]+)\]'),
897 '<div class="metatag" tag="label">\\1</div>')),
897 '<div class="metatag" tag="label">\\1</div>')),
898
898
899 # generic catch all in grey
899 # generic catch all in grey
900 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
900 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
901 '<div class="metatag" tag="generic">\\1</div>')),
901 '<div class="metatag" tag="generic">\\1</div>')),
902 ))
902 ))
903
903
904
904
905 def extract_metatags(value):
905 def extract_metatags(value):
906 """
906 """
907 Extract supported meta-tags from given text value
907 Extract supported meta-tags from given text value
908 """
908 """
909 tags = []
909 tags = []
910 if not value:
910 if not value:
911 return tags, ''
911 return tags, ''
912
912
913 for key, val in tags_paterns.items():
913 for key, val in tags_paterns.items():
914 pat, replace_html = val
914 pat, replace_html = val
915 tags.extend([(key, x.group()) for x in pat.finditer(value)])
915 tags.extend([(key, x.group()) for x in pat.finditer(value)])
916 value = pat.sub('', value)
916 value = pat.sub('', value)
917
917
918 return tags, value
918 return tags, value
919
919
920
920
921 def style_metatag(tag_type, value):
921 def style_metatag(tag_type, value):
922 """
922 """
923 converts tags from value into html equivalent
923 converts tags from value into html equivalent
924 """
924 """
925 if not value:
925 if not value:
926 return ''
926 return ''
927
927
928 html_value = value
928 html_value = value
929 tag_data = tags_paterns.get(tag_type)
929 tag_data = tags_paterns.get(tag_type)
930 if tag_data:
930 if tag_data:
931 pat, replace_html = tag_data
931 pat, replace_html = tag_data
932 # convert to plain `unicode` instead of a markup tag to be used in
932 # convert to plain `unicode` instead of a markup tag to be used in
933 # regex expressions. safe_unicode doesn't work here
933 # regex expressions. safe_unicode doesn't work here
934 html_value = pat.sub(replace_html, unicode(value))
934 html_value = pat.sub(replace_html, unicode(value))
935
935
936 return html_value
936 return html_value
937
937
938
938
939 def bool2icon(value, show_at_false=True):
939 def bool2icon(value, show_at_false=True):
940 """
940 """
941 Returns boolean value of a given value, represented as html element with
941 Returns boolean value of a given value, represented as html element with
942 classes that will represent icons
942 classes that will represent icons
943
943
944 :param value: given value to convert to html node
944 :param value: given value to convert to html node
945 """
945 """
946
946
947 if value: # does bool conversion
947 if value: # does bool conversion
948 return HTML.tag('i', class_="icon-true")
948 return HTML.tag('i', class_="icon-true")
949 else: # not true as bool
949 else: # not true as bool
950 if show_at_false:
950 if show_at_false:
951 return HTML.tag('i', class_="icon-false")
951 return HTML.tag('i', class_="icon-false")
952 return HTML.tag('i')
952 return HTML.tag('i')
953
953
954 #==============================================================================
954 #==============================================================================
955 # PERMS
955 # PERMS
956 #==============================================================================
956 #==============================================================================
957 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
957 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
958 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
958 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
959 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
959 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
960 csrf_token_key
960 csrf_token_key
961
961
962
962
963 #==============================================================================
963 #==============================================================================
964 # GRAVATAR URL
964 # GRAVATAR URL
965 #==============================================================================
965 #==============================================================================
966 class InitialsGravatar(object):
966 class InitialsGravatar(object):
967 def __init__(self, email_address, first_name, last_name, size=30,
967 def __init__(self, email_address, first_name, last_name, size=30,
968 background=None, text_color='#fff'):
968 background=None, text_color='#fff'):
969 self.size = size
969 self.size = size
970 self.first_name = first_name
970 self.first_name = first_name
971 self.last_name = last_name
971 self.last_name = last_name
972 self.email_address = email_address
972 self.email_address = email_address
973 self.background = background or self.str2color(email_address)
973 self.background = background or self.str2color(email_address)
974 self.text_color = text_color
974 self.text_color = text_color
975
975
976 def get_color_bank(self):
976 def get_color_bank(self):
977 """
977 """
978 returns a predefined list of colors that gravatars can use.
978 returns a predefined list of colors that gravatars can use.
979 Those are randomized distinct colors that guarantee readability and
979 Those are randomized distinct colors that guarantee readability and
980 uniqueness.
980 uniqueness.
981
981
982 generated with: http://phrogz.net/css/distinct-colors.html
982 generated with: http://phrogz.net/css/distinct-colors.html
983 """
983 """
984 return [
984 return [
985 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
985 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
986 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
986 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
987 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
987 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
988 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
988 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
989 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
989 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
990 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
990 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
991 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
991 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
992 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
992 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
993 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
993 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
994 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
994 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
995 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
995 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
996 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
996 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
997 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
997 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
998 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
998 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
999 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
999 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1000 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1000 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1001 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1001 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1002 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1002 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1003 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1003 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1004 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1004 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1005 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1005 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1006 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1006 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1007 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1007 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1008 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1008 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1009 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1009 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1010 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1010 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1011 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1011 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1012 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1012 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1013 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1013 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1014 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1014 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1015 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1015 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1016 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1016 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1017 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1017 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1018 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1018 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1019 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1019 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1020 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1020 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1021 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1021 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1022 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1022 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1023 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1023 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1024 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1024 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1025 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1025 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1026 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1026 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1027 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1027 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1028 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1028 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1029 '#4f8c46', '#368dd9', '#5c0073'
1029 '#4f8c46', '#368dd9', '#5c0073'
1030 ]
1030 ]
1031
1031
1032 def rgb_to_hex_color(self, rgb_tuple):
1032 def rgb_to_hex_color(self, rgb_tuple):
1033 """
1033 """
1034 Converts an rgb_tuple passed to an hex color.
1034 Converts an rgb_tuple passed to an hex color.
1035
1035
1036 :param rgb_tuple: tuple with 3 ints represents rgb color space
1036 :param rgb_tuple: tuple with 3 ints represents rgb color space
1037 """
1037 """
1038 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1038 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1039
1039
1040 def email_to_int_list(self, email_str):
1040 def email_to_int_list(self, email_str):
1041 """
1041 """
1042 Get every byte of the hex digest value of email and turn it to integer.
1042 Get every byte of the hex digest value of email and turn it to integer.
1043 It's going to be always between 0-255
1043 It's going to be always between 0-255
1044 """
1044 """
1045 digest = md5_safe(email_str.lower())
1045 digest = md5_safe(email_str.lower())
1046 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1046 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1047
1047
1048 def pick_color_bank_index(self, email_str, color_bank):
1048 def pick_color_bank_index(self, email_str, color_bank):
1049 return self.email_to_int_list(email_str)[0] % len(color_bank)
1049 return self.email_to_int_list(email_str)[0] % len(color_bank)
1050
1050
1051 def str2color(self, email_str):
1051 def str2color(self, email_str):
1052 """
1052 """
1053 Tries to map in a stable algorithm an email to color
1053 Tries to map in a stable algorithm an email to color
1054
1054
1055 :param email_str:
1055 :param email_str:
1056 """
1056 """
1057 color_bank = self.get_color_bank()
1057 color_bank = self.get_color_bank()
1058 # pick position (module it's length so we always find it in the
1058 # pick position (module it's length so we always find it in the
1059 # bank even if it's smaller than 256 values
1059 # bank even if it's smaller than 256 values
1060 pos = self.pick_color_bank_index(email_str, color_bank)
1060 pos = self.pick_color_bank_index(email_str, color_bank)
1061 return color_bank[pos]
1061 return color_bank[pos]
1062
1062
1063 def normalize_email(self, email_address):
1063 def normalize_email(self, email_address):
1064 import unicodedata
1064 import unicodedata
1065 # default host used to fill in the fake/missing email
1065 # default host used to fill in the fake/missing email
1066 default_host = u'localhost'
1066 default_host = u'localhost'
1067
1067
1068 if not email_address:
1068 if not email_address:
1069 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1069 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1070
1070
1071 email_address = safe_unicode(email_address)
1071 email_address = safe_unicode(email_address)
1072
1072
1073 if u'@' not in email_address:
1073 if u'@' not in email_address:
1074 email_address = u'%s@%s' % (email_address, default_host)
1074 email_address = u'%s@%s' % (email_address, default_host)
1075
1075
1076 if email_address.endswith(u'@'):
1076 if email_address.endswith(u'@'):
1077 email_address = u'%s%s' % (email_address, default_host)
1077 email_address = u'%s%s' % (email_address, default_host)
1078
1078
1079 email_address = unicodedata.normalize('NFKD', email_address)\
1079 email_address = unicodedata.normalize('NFKD', email_address)\
1080 .encode('ascii', 'ignore')
1080 .encode('ascii', 'ignore')
1081 return email_address
1081 return email_address
1082
1082
1083 def get_initials(self):
1083 def get_initials(self):
1084 """
1084 """
1085 Returns 2 letter initials calculated based on the input.
1085 Returns 2 letter initials calculated based on the input.
1086 The algorithm picks first given email address, and takes first letter
1086 The algorithm picks first given email address, and takes first letter
1087 of part before @, and then the first letter of server name. In case
1087 of part before @, and then the first letter of server name. In case
1088 the part before @ is in a format of `somestring.somestring2` it replaces
1088 the part before @ is in a format of `somestring.somestring2` it replaces
1089 the server letter with first letter of somestring2
1089 the server letter with first letter of somestring2
1090
1090
1091 In case function was initialized with both first and lastname, this
1091 In case function was initialized with both first and lastname, this
1092 overrides the extraction from email by first letter of the first and
1092 overrides the extraction from email by first letter of the first and
1093 last name. We add special logic to that functionality, In case Full name
1093 last name. We add special logic to that functionality, In case Full name
1094 is compound, like Guido Von Rossum, we use last part of the last name
1094 is compound, like Guido Von Rossum, we use last part of the last name
1095 (Von Rossum) picking `R`.
1095 (Von Rossum) picking `R`.
1096
1096
1097 Function also normalizes the non-ascii characters to they ascii
1097 Function also normalizes the non-ascii characters to they ascii
1098 representation, eg Δ„ => A
1098 representation, eg Δ„ => A
1099 """
1099 """
1100 import unicodedata
1100 import unicodedata
1101 # replace non-ascii to ascii
1101 # replace non-ascii to ascii
1102 first_name = unicodedata.normalize(
1102 first_name = unicodedata.normalize(
1103 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1103 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1104 last_name = unicodedata.normalize(
1104 last_name = unicodedata.normalize(
1105 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1105 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1106
1106
1107 # do NFKD encoding, and also make sure email has proper format
1107 # do NFKD encoding, and also make sure email has proper format
1108 email_address = self.normalize_email(self.email_address)
1108 email_address = self.normalize_email(self.email_address)
1109
1109
1110 # first push the email initials
1110 # first push the email initials
1111 prefix, server = email_address.split('@', 1)
1111 prefix, server = email_address.split('@', 1)
1112
1112
1113 # check if prefix is maybe a 'first_name.last_name' syntax
1113 # check if prefix is maybe a 'first_name.last_name' syntax
1114 _dot_split = prefix.rsplit('.', 1)
1114 _dot_split = prefix.rsplit('.', 1)
1115 if len(_dot_split) == 2 and _dot_split[1]:
1115 if len(_dot_split) == 2 and _dot_split[1]:
1116 initials = [_dot_split[0][0], _dot_split[1][0]]
1116 initials = [_dot_split[0][0], _dot_split[1][0]]
1117 else:
1117 else:
1118 initials = [prefix[0], server[0]]
1118 initials = [prefix[0], server[0]]
1119
1119
1120 # then try to replace either first_name or last_name
1120 # then try to replace either first_name or last_name
1121 fn_letter = (first_name or " ")[0].strip()
1121 fn_letter = (first_name or " ")[0].strip()
1122 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1122 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1123
1123
1124 if fn_letter:
1124 if fn_letter:
1125 initials[0] = fn_letter
1125 initials[0] = fn_letter
1126
1126
1127 if ln_letter:
1127 if ln_letter:
1128 initials[1] = ln_letter
1128 initials[1] = ln_letter
1129
1129
1130 return ''.join(initials).upper()
1130 return ''.join(initials).upper()
1131
1131
1132 def get_img_data_by_type(self, font_family, img_type):
1132 def get_img_data_by_type(self, font_family, img_type):
1133 default_user = """
1133 default_user = """
1134 <svg xmlns="http://www.w3.org/2000/svg"
1134 <svg xmlns="http://www.w3.org/2000/svg"
1135 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1135 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1136 viewBox="-15 -10 439.165 429.164"
1136 viewBox="-15 -10 439.165 429.164"
1137
1137
1138 xml:space="preserve"
1138 xml:space="preserve"
1139 style="background:{background};" >
1139 style="background:{background};" >
1140
1140
1141 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1141 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1142 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1142 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1143 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1143 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1144 168.596,153.916,216.671,
1144 168.596,153.916,216.671,
1145 204.583,216.671z" fill="{text_color}"/>
1145 204.583,216.671z" fill="{text_color}"/>
1146 <path d="M407.164,374.717L360.88,
1146 <path d="M407.164,374.717L360.88,
1147 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1147 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1148 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1148 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1149 15.366-44.203,23.488-69.076,23.488c-24.877,
1149 15.366-44.203,23.488-69.076,23.488c-24.877,
1150 0-48.762-8.122-69.078-23.488
1150 0-48.762-8.122-69.078-23.488
1151 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1151 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1152 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1152 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1153 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1153 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1154 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1154 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1155 19.402-10.527 C409.699,390.129,
1155 19.402-10.527 C409.699,390.129,
1156 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1156 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1157 </svg>""".format(
1157 </svg>""".format(
1158 size=self.size,
1158 size=self.size,
1159 background='#979797', # @grey4
1159 background='#979797', # @grey4
1160 text_color=self.text_color,
1160 text_color=self.text_color,
1161 font_family=font_family)
1161 font_family=font_family)
1162
1162
1163 return {
1163 return {
1164 "default_user": default_user
1164 "default_user": default_user
1165 }[img_type]
1165 }[img_type]
1166
1166
1167 def get_img_data(self, svg_type=None):
1167 def get_img_data(self, svg_type=None):
1168 """
1168 """
1169 generates the svg metadata for image
1169 generates the svg metadata for image
1170 """
1170 """
1171 fonts = [
1171 fonts = [
1172 '-apple-system',
1172 '-apple-system',
1173 'BlinkMacSystemFont',
1173 'BlinkMacSystemFont',
1174 'Segoe UI',
1174 'Segoe UI',
1175 'Roboto',
1175 'Roboto',
1176 'Oxygen-Sans',
1176 'Oxygen-Sans',
1177 'Ubuntu',
1177 'Ubuntu',
1178 'Cantarell',
1178 'Cantarell',
1179 'Helvetica Neue',
1179 'Helvetica Neue',
1180 'sans-serif'
1180 'sans-serif'
1181 ]
1181 ]
1182 font_family = ','.join(fonts)
1182 font_family = ','.join(fonts)
1183 if svg_type:
1183 if svg_type:
1184 return self.get_img_data_by_type(font_family, svg_type)
1184 return self.get_img_data_by_type(font_family, svg_type)
1185
1185
1186 initials = self.get_initials()
1186 initials = self.get_initials()
1187 img_data = """
1187 img_data = """
1188 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1188 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1189 width="{size}" height="{size}"
1189 width="{size}" height="{size}"
1190 style="width: 100%; height: 100%; background-color: {background}"
1190 style="width: 100%; height: 100%; background-color: {background}"
1191 viewBox="0 0 {size} {size}">
1191 viewBox="0 0 {size} {size}">
1192 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1192 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1193 pointer-events="auto" fill="{text_color}"
1193 pointer-events="auto" fill="{text_color}"
1194 font-family="{font_family}"
1194 font-family="{font_family}"
1195 style="font-weight: 400; font-size: {f_size}px;">{text}
1195 style="font-weight: 400; font-size: {f_size}px;">{text}
1196 </text>
1196 </text>
1197 </svg>""".format(
1197 </svg>""".format(
1198 size=self.size,
1198 size=self.size,
1199 f_size=self.size/1.85, # scale the text inside the box nicely
1199 f_size=self.size/1.85, # scale the text inside the box nicely
1200 background=self.background,
1200 background=self.background,
1201 text_color=self.text_color,
1201 text_color=self.text_color,
1202 text=initials.upper(),
1202 text=initials.upper(),
1203 font_family=font_family)
1203 font_family=font_family)
1204
1204
1205 return img_data
1205 return img_data
1206
1206
1207 def generate_svg(self, svg_type=None):
1207 def generate_svg(self, svg_type=None):
1208 img_data = self.get_img_data(svg_type)
1208 img_data = self.get_img_data(svg_type)
1209 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1209 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1210
1210
1211
1211
1212 def initials_gravatar(email_address, first_name, last_name, size=30):
1212 def initials_gravatar(email_address, first_name, last_name, size=30):
1213 svg_type = None
1213 svg_type = None
1214 if email_address == User.DEFAULT_USER_EMAIL:
1214 if email_address == User.DEFAULT_USER_EMAIL:
1215 svg_type = 'default_user'
1215 svg_type = 'default_user'
1216 klass = InitialsGravatar(email_address, first_name, last_name, size)
1216 klass = InitialsGravatar(email_address, first_name, last_name, size)
1217 return klass.generate_svg(svg_type=svg_type)
1217 return klass.generate_svg(svg_type=svg_type)
1218
1218
1219
1219
1220 def gravatar_url(email_address, size=30, request=None):
1220 def gravatar_url(email_address, size=30, request=None):
1221 request = get_current_request()
1221 request = get_current_request()
1222 _use_gravatar = request.call_context.visual.use_gravatar
1222 _use_gravatar = request.call_context.visual.use_gravatar
1223 _gravatar_url = request.call_context.visual.gravatar_url
1223 _gravatar_url = request.call_context.visual.gravatar_url
1224
1224
1225 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1225 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1226
1226
1227 email_address = email_address or User.DEFAULT_USER_EMAIL
1227 email_address = email_address or User.DEFAULT_USER_EMAIL
1228 if isinstance(email_address, unicode):
1228 if isinstance(email_address, unicode):
1229 # hashlib crashes on unicode items
1229 # hashlib crashes on unicode items
1230 email_address = safe_str(email_address)
1230 email_address = safe_str(email_address)
1231
1231
1232 # empty email or default user
1232 # empty email or default user
1233 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1233 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1234 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1234 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1235
1235
1236 if _use_gravatar:
1236 if _use_gravatar:
1237 # TODO: Disuse pyramid thread locals. Think about another solution to
1237 # TODO: Disuse pyramid thread locals. Think about another solution to
1238 # get the host and schema here.
1238 # get the host and schema here.
1239 request = get_current_request()
1239 request = get_current_request()
1240 tmpl = safe_str(_gravatar_url)
1240 tmpl = safe_str(_gravatar_url)
1241 tmpl = tmpl.replace('{email}', email_address)\
1241 tmpl = tmpl.replace('{email}', email_address)\
1242 .replace('{md5email}', md5_safe(email_address.lower())) \
1242 .replace('{md5email}', md5_safe(email_address.lower())) \
1243 .replace('{netloc}', request.host)\
1243 .replace('{netloc}', request.host)\
1244 .replace('{scheme}', request.scheme)\
1244 .replace('{scheme}', request.scheme)\
1245 .replace('{size}', safe_str(size))
1245 .replace('{size}', safe_str(size))
1246 return tmpl
1246 return tmpl
1247 else:
1247 else:
1248 return initials_gravatar(email_address, '', '', size=size)
1248 return initials_gravatar(email_address, '', '', size=size)
1249
1249
1250
1250
1251 class Page(_Page):
1251 class Page(_Page):
1252 """
1252 """
1253 Custom pager to match rendering style with paginator
1253 Custom pager to match rendering style with paginator
1254 """
1254 """
1255
1255
1256 def _get_pos(self, cur_page, max_page, items):
1256 def _get_pos(self, cur_page, max_page, items):
1257 edge = (items / 2) + 1
1257 edge = (items / 2) + 1
1258 if (cur_page <= edge):
1258 if (cur_page <= edge):
1259 radius = max(items / 2, items - cur_page)
1259 radius = max(items / 2, items - cur_page)
1260 elif (max_page - cur_page) < edge:
1260 elif (max_page - cur_page) < edge:
1261 radius = (items - 1) - (max_page - cur_page)
1261 radius = (items - 1) - (max_page - cur_page)
1262 else:
1262 else:
1263 radius = items / 2
1263 radius = items / 2
1264
1264
1265 left = max(1, (cur_page - (radius)))
1265 left = max(1, (cur_page - (radius)))
1266 right = min(max_page, cur_page + (radius))
1266 right = min(max_page, cur_page + (radius))
1267 return left, cur_page, right
1267 return left, cur_page, right
1268
1268
1269 def _range(self, regexp_match):
1269 def _range(self, regexp_match):
1270 """
1270 """
1271 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1271 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1272
1272
1273 Arguments:
1273 Arguments:
1274
1274
1275 regexp_match
1275 regexp_match
1276 A "re" (regular expressions) match object containing the
1276 A "re" (regular expressions) match object containing the
1277 radius of linked pages around the current page in
1277 radius of linked pages around the current page in
1278 regexp_match.group(1) as a string
1278 regexp_match.group(1) as a string
1279
1279
1280 This function is supposed to be called as a callable in
1280 This function is supposed to be called as a callable in
1281 re.sub.
1281 re.sub.
1282
1282
1283 """
1283 """
1284 radius = int(regexp_match.group(1))
1284 radius = int(regexp_match.group(1))
1285
1285
1286 # Compute the first and last page number within the radius
1286 # Compute the first and last page number within the radius
1287 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1287 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1288 # -> leftmost_page = 5
1288 # -> leftmost_page = 5
1289 # -> rightmost_page = 9
1289 # -> rightmost_page = 9
1290 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1290 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1291 self.last_page,
1291 self.last_page,
1292 (radius * 2) + 1)
1292 (radius * 2) + 1)
1293 nav_items = []
1293 nav_items = []
1294
1294
1295 # Create a link to the first page (unless we are on the first page
1295 # Create a link to the first page (unless we are on the first page
1296 # or there would be no need to insert '..' spacers)
1296 # or there would be no need to insert '..' spacers)
1297 if self.page != self.first_page and self.first_page < leftmost_page:
1297 if self.page != self.first_page and self.first_page < leftmost_page:
1298 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1298 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1299
1299
1300 # Insert dots if there are pages between the first page
1300 # Insert dots if there are pages between the first page
1301 # and the currently displayed page range
1301 # and the currently displayed page range
1302 if leftmost_page - self.first_page > 1:
1302 if leftmost_page - self.first_page > 1:
1303 # Wrap in a SPAN tag if nolink_attr is set
1303 # Wrap in a SPAN tag if nolink_attr is set
1304 text = '..'
1304 text = '..'
1305 if self.dotdot_attr:
1305 if self.dotdot_attr:
1306 text = HTML.span(c=text, **self.dotdot_attr)
1306 text = HTML.span(c=text, **self.dotdot_attr)
1307 nav_items.append(text)
1307 nav_items.append(text)
1308
1308
1309 for thispage in xrange(leftmost_page, rightmost_page + 1):
1309 for thispage in xrange(leftmost_page, rightmost_page + 1):
1310 # Hilight the current page number and do not use a link
1310 # Hilight the current page number and do not use a link
1311 if thispage == self.page:
1311 if thispage == self.page:
1312 text = '%s' % (thispage,)
1312 text = '%s' % (thispage,)
1313 # Wrap in a SPAN tag if nolink_attr is set
1313 # Wrap in a SPAN tag if nolink_attr is set
1314 if self.curpage_attr:
1314 if self.curpage_attr:
1315 text = HTML.span(c=text, **self.curpage_attr)
1315 text = HTML.span(c=text, **self.curpage_attr)
1316 nav_items.append(text)
1316 nav_items.append(text)
1317 # Otherwise create just a link to that page
1317 # Otherwise create just a link to that page
1318 else:
1318 else:
1319 text = '%s' % (thispage,)
1319 text = '%s' % (thispage,)
1320 nav_items.append(self._pagerlink(thispage, text))
1320 nav_items.append(self._pagerlink(thispage, text))
1321
1321
1322 # Insert dots if there are pages between the displayed
1322 # Insert dots if there are pages between the displayed
1323 # page numbers and the end of the page range
1323 # page numbers and the end of the page range
1324 if self.last_page - rightmost_page > 1:
1324 if self.last_page - rightmost_page > 1:
1325 text = '..'
1325 text = '..'
1326 # Wrap in a SPAN tag if nolink_attr is set
1326 # Wrap in a SPAN tag if nolink_attr is set
1327 if self.dotdot_attr:
1327 if self.dotdot_attr:
1328 text = HTML.span(c=text, **self.dotdot_attr)
1328 text = HTML.span(c=text, **self.dotdot_attr)
1329 nav_items.append(text)
1329 nav_items.append(text)
1330
1330
1331 # Create a link to the very last page (unless we are on the last
1331 # Create a link to the very last page (unless we are on the last
1332 # page or there would be no need to insert '..' spacers)
1332 # page or there would be no need to insert '..' spacers)
1333 if self.page != self.last_page and rightmost_page < self.last_page:
1333 if self.page != self.last_page and rightmost_page < self.last_page:
1334 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1334 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1335
1335
1336 ## prerender links
1336 ## prerender links
1337 #_page_link = url.current()
1337 #_page_link = url.current()
1338 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1338 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1339 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1339 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1340 return self.separator.join(nav_items)
1340 return self.separator.join(nav_items)
1341
1341
1342 def pager(self, format='~2~', page_param='page', partial_param='partial',
1342 def pager(self, format='~2~', page_param='page', partial_param='partial',
1343 show_if_single_page=False, separator=' ', onclick=None,
1343 show_if_single_page=False, separator=' ', onclick=None,
1344 symbol_first='<<', symbol_last='>>',
1344 symbol_first='<<', symbol_last='>>',
1345 symbol_previous='<', symbol_next='>',
1345 symbol_previous='<', symbol_next='>',
1346 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1346 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1347 curpage_attr={'class': 'pager_curpage'},
1347 curpage_attr={'class': 'pager_curpage'},
1348 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1348 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1349
1349
1350 self.curpage_attr = curpage_attr
1350 self.curpage_attr = curpage_attr
1351 self.separator = separator
1351 self.separator = separator
1352 self.pager_kwargs = kwargs
1352 self.pager_kwargs = kwargs
1353 self.page_param = page_param
1353 self.page_param = page_param
1354 self.partial_param = partial_param
1354 self.partial_param = partial_param
1355 self.onclick = onclick
1355 self.onclick = onclick
1356 self.link_attr = link_attr
1356 self.link_attr = link_attr
1357 self.dotdot_attr = dotdot_attr
1357 self.dotdot_attr = dotdot_attr
1358
1358
1359 # Don't show navigator if there is no more than one page
1359 # Don't show navigator if there is no more than one page
1360 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1360 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1361 return ''
1361 return ''
1362
1362
1363 from string import Template
1363 from string import Template
1364 # Replace ~...~ in token format by range of pages
1364 # Replace ~...~ in token format by range of pages
1365 result = re.sub(r'~(\d+)~', self._range, format)
1365 result = re.sub(r'~(\d+)~', self._range, format)
1366
1366
1367 # Interpolate '%' variables
1367 # Interpolate '%' variables
1368 result = Template(result).safe_substitute({
1368 result = Template(result).safe_substitute({
1369 'first_page': self.first_page,
1369 'first_page': self.first_page,
1370 'last_page': self.last_page,
1370 'last_page': self.last_page,
1371 'page': self.page,
1371 'page': self.page,
1372 'page_count': self.page_count,
1372 'page_count': self.page_count,
1373 'items_per_page': self.items_per_page,
1373 'items_per_page': self.items_per_page,
1374 'first_item': self.first_item,
1374 'first_item': self.first_item,
1375 'last_item': self.last_item,
1375 'last_item': self.last_item,
1376 'item_count': self.item_count,
1376 'item_count': self.item_count,
1377 'link_first': self.page > self.first_page and \
1377 'link_first': self.page > self.first_page and \
1378 self._pagerlink(self.first_page, symbol_first) or '',
1378 self._pagerlink(self.first_page, symbol_first) or '',
1379 'link_last': self.page < self.last_page and \
1379 'link_last': self.page < self.last_page and \
1380 self._pagerlink(self.last_page, symbol_last) or '',
1380 self._pagerlink(self.last_page, symbol_last) or '',
1381 'link_previous': self.previous_page and \
1381 'link_previous': self.previous_page and \
1382 self._pagerlink(self.previous_page, symbol_previous) \
1382 self._pagerlink(self.previous_page, symbol_previous) \
1383 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1383 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1384 'link_next': self.next_page and \
1384 'link_next': self.next_page and \
1385 self._pagerlink(self.next_page, symbol_next) \
1385 self._pagerlink(self.next_page, symbol_next) \
1386 or HTML.span(symbol_next, class_="pg-next disabled")
1386 or HTML.span(symbol_next, class_="pg-next disabled")
1387 })
1387 })
1388
1388
1389 return literal(result)
1389 return literal(result)
1390
1390
1391
1391
1392 #==============================================================================
1392 #==============================================================================
1393 # REPO PAGER, PAGER FOR REPOSITORY
1393 # REPO PAGER, PAGER FOR REPOSITORY
1394 #==============================================================================
1394 #==============================================================================
1395 class RepoPage(Page):
1395 class RepoPage(Page):
1396
1396
1397 def __init__(self, collection, page=1, items_per_page=20,
1397 def __init__(self, collection, page=1, items_per_page=20,
1398 item_count=None, url=None, **kwargs):
1398 item_count=None, url=None, **kwargs):
1399
1399
1400 """Create a "RepoPage" instance. special pager for paging
1400 """Create a "RepoPage" instance. special pager for paging
1401 repository
1401 repository
1402 """
1402 """
1403 self._url_generator = url
1403 self._url_generator = url
1404
1404
1405 # Safe the kwargs class-wide so they can be used in the pager() method
1405 # Safe the kwargs class-wide so they can be used in the pager() method
1406 self.kwargs = kwargs
1406 self.kwargs = kwargs
1407
1407
1408 # Save a reference to the collection
1408 # Save a reference to the collection
1409 self.original_collection = collection
1409 self.original_collection = collection
1410
1410
1411 self.collection = collection
1411 self.collection = collection
1412
1412
1413 # The self.page is the number of the current page.
1413 # The self.page is the number of the current page.
1414 # The first page has the number 1!
1414 # The first page has the number 1!
1415 try:
1415 try:
1416 self.page = int(page) # make it int() if we get it as a string
1416 self.page = int(page) # make it int() if we get it as a string
1417 except (ValueError, TypeError):
1417 except (ValueError, TypeError):
1418 self.page = 1
1418 self.page = 1
1419
1419
1420 self.items_per_page = items_per_page
1420 self.items_per_page = items_per_page
1421
1421
1422 # Unless the user tells us how many items the collections has
1422 # Unless the user tells us how many items the collections has
1423 # we calculate that ourselves.
1423 # we calculate that ourselves.
1424 if item_count is not None:
1424 if item_count is not None:
1425 self.item_count = item_count
1425 self.item_count = item_count
1426 else:
1426 else:
1427 self.item_count = len(self.collection)
1427 self.item_count = len(self.collection)
1428
1428
1429 # Compute the number of the first and last available page
1429 # Compute the number of the first and last available page
1430 if self.item_count > 0:
1430 if self.item_count > 0:
1431 self.first_page = 1
1431 self.first_page = 1
1432 self.page_count = int(math.ceil(float(self.item_count) /
1432 self.page_count = int(math.ceil(float(self.item_count) /
1433 self.items_per_page))
1433 self.items_per_page))
1434 self.last_page = self.first_page + self.page_count - 1
1434 self.last_page = self.first_page + self.page_count - 1
1435
1435
1436 # Make sure that the requested page number is the range of
1436 # Make sure that the requested page number is the range of
1437 # valid pages
1437 # valid pages
1438 if self.page > self.last_page:
1438 if self.page > self.last_page:
1439 self.page = self.last_page
1439 self.page = self.last_page
1440 elif self.page < self.first_page:
1440 elif self.page < self.first_page:
1441 self.page = self.first_page
1441 self.page = self.first_page
1442
1442
1443 # Note: the number of items on this page can be less than
1443 # Note: the number of items on this page can be less than
1444 # items_per_page if the last page is not full
1444 # items_per_page if the last page is not full
1445 self.first_item = max(0, (self.item_count) - (self.page *
1445 self.first_item = max(0, (self.item_count) - (self.page *
1446 items_per_page))
1446 items_per_page))
1447 self.last_item = ((self.item_count - 1) - items_per_page *
1447 self.last_item = ((self.item_count - 1) - items_per_page *
1448 (self.page - 1))
1448 (self.page - 1))
1449
1449
1450 self.items = list(self.collection[self.first_item:self.last_item + 1])
1450 self.items = list(self.collection[self.first_item:self.last_item + 1])
1451
1451
1452 # Links to previous and next page
1452 # Links to previous and next page
1453 if self.page > self.first_page:
1453 if self.page > self.first_page:
1454 self.previous_page = self.page - 1
1454 self.previous_page = self.page - 1
1455 else:
1455 else:
1456 self.previous_page = None
1456 self.previous_page = None
1457
1457
1458 if self.page < self.last_page:
1458 if self.page < self.last_page:
1459 self.next_page = self.page + 1
1459 self.next_page = self.page + 1
1460 else:
1460 else:
1461 self.next_page = None
1461 self.next_page = None
1462
1462
1463 # No items available
1463 # No items available
1464 else:
1464 else:
1465 self.first_page = None
1465 self.first_page = None
1466 self.page_count = 0
1466 self.page_count = 0
1467 self.last_page = None
1467 self.last_page = None
1468 self.first_item = None
1468 self.first_item = None
1469 self.last_item = None
1469 self.last_item = None
1470 self.previous_page = None
1470 self.previous_page = None
1471 self.next_page = None
1471 self.next_page = None
1472 self.items = []
1472 self.items = []
1473
1473
1474 # This is a subclass of the 'list' type. Initialise the list now.
1474 # This is a subclass of the 'list' type. Initialise the list now.
1475 list.__init__(self, reversed(self.items))
1475 list.__init__(self, reversed(self.items))
1476
1476
1477
1477
1478 def breadcrumb_repo_link(repo):
1478 def breadcrumb_repo_link(repo):
1479 """
1479 """
1480 Makes a breadcrumbs path link to repo
1480 Makes a breadcrumbs path link to repo
1481
1481
1482 ex::
1482 ex::
1483 group >> subgroup >> repo
1483 group >> subgroup >> repo
1484
1484
1485 :param repo: a Repository instance
1485 :param repo: a Repository instance
1486 """
1486 """
1487
1487
1488 path = [
1488 path = [
1489 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1489 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1490 for group in repo.groups_with_parents
1490 for group in repo.groups_with_parents
1491 ] + [
1491 ] + [
1492 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1492 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1493 ]
1493 ]
1494
1494
1495 return literal(' &raquo; '.join(path))
1495 return literal(' &raquo; '.join(path))
1496
1496
1497
1497
1498 def breadcrumb_repo_group_link(repo_group):
1498 def breadcrumb_repo_group_link(repo_group):
1499 """
1499 """
1500 Makes a breadcrumbs path link to repo
1500 Makes a breadcrumbs path link to repo
1501
1501
1502 ex::
1502 ex::
1503 group >> subgroup
1503 group >> subgroup
1504
1504
1505 :param repo_group: a Repository Group instance
1505 :param repo_group: a Repository Group instance
1506 """
1506 """
1507
1507
1508 path = [
1508 path = [
1509 link_to(group.name,
1509 link_to(group.name,
1510 route_path('repo_group_home', repo_group_name=group.group_name))
1510 route_path('repo_group_home', repo_group_name=group.group_name))
1511 for group in repo_group.parents
1511 for group in repo_group.parents
1512 ] + [
1512 ] + [
1513 link_to(repo_group.name,
1513 link_to(repo_group.name,
1514 route_path('repo_group_home', repo_group_name=repo_group.group_name))
1514 route_path('repo_group_home', repo_group_name=repo_group.group_name))
1515 ]
1515 ]
1516
1516
1517 return literal(' &raquo; '.join(path))
1517 return literal(' &raquo; '.join(path))
1518
1518
1519
1519
1520 def format_byte_size_binary(file_size):
1520 def format_byte_size_binary(file_size):
1521 """
1521 """
1522 Formats file/folder sizes to standard.
1522 Formats file/folder sizes to standard.
1523 """
1523 """
1524 if file_size is None:
1524 if file_size is None:
1525 file_size = 0
1525 file_size = 0
1526
1526
1527 formatted_size = format_byte_size(file_size, binary=True)
1527 formatted_size = format_byte_size(file_size, binary=True)
1528 return formatted_size
1528 return formatted_size
1529
1529
1530
1530
1531 def urlify_text(text_, safe=True):
1531 def urlify_text(text_, safe=True):
1532 """
1532 """
1533 Extrac urls from text and make html links out of them
1533 Extrac urls from text and make html links out of them
1534
1534
1535 :param text_:
1535 :param text_:
1536 """
1536 """
1537
1537
1538 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1538 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1539 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1539 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1540
1540
1541 def url_func(match_obj):
1541 def url_func(match_obj):
1542 url_full = match_obj.groups()[0]
1542 url_full = match_obj.groups()[0]
1543 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1543 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1544 _newtext = url_pat.sub(url_func, text_)
1544 _newtext = url_pat.sub(url_func, text_)
1545 if safe:
1545 if safe:
1546 return literal(_newtext)
1546 return literal(_newtext)
1547 return _newtext
1547 return _newtext
1548
1548
1549
1549
1550 def urlify_commits(text_, repository):
1550 def urlify_commits(text_, repository):
1551 """
1551 """
1552 Extract commit ids from text and make link from them
1552 Extract commit ids from text and make link from them
1553
1553
1554 :param text_:
1554 :param text_:
1555 :param repository: repo name to build the URL with
1555 :param repository: repo name to build the URL with
1556 """
1556 """
1557
1557
1558 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1558 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1559
1559
1560 def url_func(match_obj):
1560 def url_func(match_obj):
1561 commit_id = match_obj.groups()[1]
1561 commit_id = match_obj.groups()[1]
1562 pref = match_obj.groups()[0]
1562 pref = match_obj.groups()[0]
1563 suf = match_obj.groups()[2]
1563 suf = match_obj.groups()[2]
1564
1564
1565 tmpl = (
1565 tmpl = (
1566 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1566 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1567 '%(commit_id)s</a>%(suf)s'
1567 '%(commit_id)s</a>%(suf)s'
1568 )
1568 )
1569 return tmpl % {
1569 return tmpl % {
1570 'pref': pref,
1570 'pref': pref,
1571 'cls': 'revision-link',
1571 'cls': 'revision-link',
1572 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1572 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1573 'commit_id': commit_id,
1573 'commit_id': commit_id,
1574 'suf': suf
1574 'suf': suf
1575 }
1575 }
1576
1576
1577 newtext = URL_PAT.sub(url_func, text_)
1577 newtext = URL_PAT.sub(url_func, text_)
1578
1578
1579 return newtext
1579 return newtext
1580
1580
1581
1581
1582 def _process_url_func(match_obj, repo_name, uid, entry,
1582 def _process_url_func(match_obj, repo_name, uid, entry,
1583 return_raw_data=False, link_format='html'):
1583 return_raw_data=False, link_format='html'):
1584 pref = ''
1584 pref = ''
1585 if match_obj.group().startswith(' '):
1585 if match_obj.group().startswith(' '):
1586 pref = ' '
1586 pref = ' '
1587
1587
1588 issue_id = ''.join(match_obj.groups())
1588 issue_id = ''.join(match_obj.groups())
1589
1589
1590 if link_format == 'html':
1590 if link_format == 'html':
1591 tmpl = (
1591 tmpl = (
1592 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1592 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1593 '%(issue-prefix)s%(id-repr)s'
1593 '%(issue-prefix)s%(id-repr)s'
1594 '</a>')
1594 '</a>')
1595 elif link_format == 'rst':
1595 elif link_format == 'rst':
1596 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1596 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1597 elif link_format == 'markdown':
1597 elif link_format == 'markdown':
1598 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1598 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1599 else:
1599 else:
1600 raise ValueError('Bad link_format:{}'.format(link_format))
1600 raise ValueError('Bad link_format:{}'.format(link_format))
1601
1601
1602 (repo_name_cleaned,
1602 (repo_name_cleaned,
1603 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1603 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1604
1604
1605 # variables replacement
1605 # variables replacement
1606 named_vars = {
1606 named_vars = {
1607 'id': issue_id,
1607 'id': issue_id,
1608 'repo': repo_name,
1608 'repo': repo_name,
1609 'repo_name': repo_name_cleaned,
1609 'repo_name': repo_name_cleaned,
1610 'group_name': parent_group_name
1610 'group_name': parent_group_name
1611 }
1611 }
1612 # named regex variables
1612 # named regex variables
1613 named_vars.update(match_obj.groupdict())
1613 named_vars.update(match_obj.groupdict())
1614 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1614 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1615
1615
1616 def quote_cleaner(input_str):
1616 def quote_cleaner(input_str):
1617 """Remove quotes as it's HTML"""
1617 """Remove quotes as it's HTML"""
1618 return input_str.replace('"', '')
1618 return input_str.replace('"', '')
1619
1619
1620 data = {
1620 data = {
1621 'pref': pref,
1621 'pref': pref,
1622 'cls': quote_cleaner('issue-tracker-link'),
1622 'cls': quote_cleaner('issue-tracker-link'),
1623 'url': quote_cleaner(_url),
1623 'url': quote_cleaner(_url),
1624 'id-repr': issue_id,
1624 'id-repr': issue_id,
1625 'issue-prefix': entry['pref'],
1625 'issue-prefix': entry['pref'],
1626 'serv': entry['url'],
1626 'serv': entry['url'],
1627 }
1627 }
1628 if return_raw_data:
1628 if return_raw_data:
1629 return {
1629 return {
1630 'id': issue_id,
1630 'id': issue_id,
1631 'url': _url
1631 'url': _url
1632 }
1632 }
1633 return tmpl % data
1633 return tmpl % data
1634
1634
1635
1635
1636 def get_active_pattern_entries(repo_name):
1636 def get_active_pattern_entries(repo_name):
1637 repo = None
1637 repo = None
1638 if repo_name:
1638 if repo_name:
1639 # Retrieving repo_name to avoid invalid repo_name to explode on
1639 # Retrieving repo_name to avoid invalid repo_name to explode on
1640 # IssueTrackerSettingsModel but still passing invalid name further down
1640 # IssueTrackerSettingsModel but still passing invalid name further down
1641 repo = Repository.get_by_repo_name(repo_name, cache=True)
1641 repo = Repository.get_by_repo_name(repo_name, cache=True)
1642
1642
1643 settings_model = IssueTrackerSettingsModel(repo=repo)
1643 settings_model = IssueTrackerSettingsModel(repo=repo)
1644 active_entries = settings_model.get_settings(cache=True)
1644 active_entries = settings_model.get_settings(cache=True)
1645 return active_entries
1645 return active_entries
1646
1646
1647
1647
1648 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1648 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1649
1649
1650 allowed_formats = ['html', 'rst', 'markdown']
1650 allowed_formats = ['html', 'rst', 'markdown']
1651 if link_format not in allowed_formats:
1651 if link_format not in allowed_formats:
1652 raise ValueError('Link format can be only one of:{} got {}'.format(
1652 raise ValueError('Link format can be only one of:{} got {}'.format(
1653 allowed_formats, link_format))
1653 allowed_formats, link_format))
1654
1654
1655 active_entries = active_entries or get_active_pattern_entries(repo_name)
1655 active_entries = active_entries or get_active_pattern_entries(repo_name)
1656 issues_data = []
1656 issues_data = []
1657 newtext = text_string
1657 newtext = text_string
1658
1658
1659 for uid, entry in active_entries.items():
1659 for uid, entry in active_entries.items():
1660 log.debug('found issue tracker entry with uid %s', uid)
1660 log.debug('found issue tracker entry with uid %s', uid)
1661
1661
1662 if not (entry['pat'] and entry['url']):
1662 if not (entry['pat'] and entry['url']):
1663 log.debug('skipping due to missing data')
1663 log.debug('skipping due to missing data')
1664 continue
1664 continue
1665
1665
1666 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1666 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1667 uid, entry['pat'], entry['url'], entry['pref'])
1667 uid, entry['pat'], entry['url'], entry['pref'])
1668
1668
1669 try:
1669 try:
1670 pattern = re.compile(r'%s' % entry['pat'])
1670 pattern = re.compile(r'%s' % entry['pat'])
1671 except re.error:
1671 except re.error:
1672 log.exception(
1672 log.exception(
1673 'issue tracker pattern: `%s` failed to compile',
1673 'issue tracker pattern: `%s` failed to compile',
1674 entry['pat'])
1674 entry['pat'])
1675 continue
1675 continue
1676
1676
1677 data_func = partial(
1677 data_func = partial(
1678 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1678 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1679 return_raw_data=True)
1679 return_raw_data=True)
1680
1680
1681 for match_obj in pattern.finditer(text_string):
1681 for match_obj in pattern.finditer(text_string):
1682 issues_data.append(data_func(match_obj))
1682 issues_data.append(data_func(match_obj))
1683
1683
1684 url_func = partial(
1684 url_func = partial(
1685 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1685 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1686 link_format=link_format)
1686 link_format=link_format)
1687
1687
1688 newtext = pattern.sub(url_func, newtext)
1688 newtext = pattern.sub(url_func, newtext)
1689 log.debug('processed prefix:uid `%s`', uid)
1689 log.debug('processed prefix:uid `%s`', uid)
1690
1690
1691 return newtext, issues_data
1691 return newtext, issues_data
1692
1692
1693
1693
1694 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1694 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1695 """
1695 """
1696 Parses given text message and makes proper links.
1696 Parses given text message and makes proper links.
1697 issues are linked to given issue-server, and rest is a commit link
1697 issues are linked to given issue-server, and rest is a commit link
1698
1698
1699 :param commit_text:
1699 :param commit_text:
1700 :param repository:
1700 :param repository:
1701 """
1701 """
1702 def escaper(string):
1702 def escaper(string):
1703 return string.replace('<', '&lt;').replace('>', '&gt;')
1703 return string.replace('<', '&lt;').replace('>', '&gt;')
1704
1704
1705 newtext = escaper(commit_text)
1705 newtext = escaper(commit_text)
1706
1706
1707 # extract http/https links and make them real urls
1707 # extract http/https links and make them real urls
1708 newtext = urlify_text(newtext, safe=False)
1708 newtext = urlify_text(newtext, safe=False)
1709
1709
1710 # urlify commits - extract commit ids and make link out of them, if we have
1710 # urlify commits - extract commit ids and make link out of them, if we have
1711 # the scope of repository present.
1711 # the scope of repository present.
1712 if repository:
1712 if repository:
1713 newtext = urlify_commits(newtext, repository)
1713 newtext = urlify_commits(newtext, repository)
1714
1714
1715 # process issue tracker patterns
1715 # process issue tracker patterns
1716 newtext, issues = process_patterns(newtext, repository or '',
1716 newtext, issues = process_patterns(newtext, repository or '',
1717 active_entries=active_pattern_entries)
1717 active_entries=active_pattern_entries)
1718
1718
1719 return literal(newtext)
1719 return literal(newtext)
1720
1720
1721
1721
1722 def render_binary(repo_name, file_obj):
1722 def render_binary(repo_name, file_obj):
1723 """
1723 """
1724 Choose how to render a binary file
1724 Choose how to render a binary file
1725 """
1725 """
1726
1726
1727 filename = file_obj.name
1727 filename = file_obj.name
1728
1728
1729 # images
1729 # images
1730 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1730 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1731 if fnmatch.fnmatch(filename, pat=ext):
1731 if fnmatch.fnmatch(filename, pat=ext):
1732 alt = escape(filename)
1732 alt = escape(filename)
1733 src = route_path(
1733 src = route_path(
1734 'repo_file_raw', repo_name=repo_name,
1734 'repo_file_raw', repo_name=repo_name,
1735 commit_id=file_obj.commit.raw_id,
1735 commit_id=file_obj.commit.raw_id,
1736 f_path=file_obj.path)
1736 f_path=file_obj.path)
1737 return literal(
1737 return literal(
1738 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1738 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1739
1739
1740
1740
1741 def renderer_from_filename(filename, exclude=None):
1741 def renderer_from_filename(filename, exclude=None):
1742 """
1742 """
1743 choose a renderer based on filename, this works only for text based files
1743 choose a renderer based on filename, this works only for text based files
1744 """
1744 """
1745
1745
1746 # ipython
1746 # ipython
1747 for ext in ['*.ipynb']:
1747 for ext in ['*.ipynb']:
1748 if fnmatch.fnmatch(filename, pat=ext):
1748 if fnmatch.fnmatch(filename, pat=ext):
1749 return 'jupyter'
1749 return 'jupyter'
1750
1750
1751 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1751 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1752 if is_markup:
1752 if is_markup:
1753 return is_markup
1753 return is_markup
1754 return None
1754 return None
1755
1755
1756
1756
1757 def render(source, renderer='rst', mentions=False, relative_urls=None,
1757 def render(source, renderer='rst', mentions=False, relative_urls=None,
1758 repo_name=None):
1758 repo_name=None):
1759
1759
1760 def maybe_convert_relative_links(html_source):
1760 def maybe_convert_relative_links(html_source):
1761 if relative_urls:
1761 if relative_urls:
1762 return relative_links(html_source, relative_urls)
1762 return relative_links(html_source, relative_urls)
1763 return html_source
1763 return html_source
1764
1764
1765 if renderer == 'plain':
1765 if renderer == 'plain':
1766 return literal(
1766 return literal(
1767 MarkupRenderer.plain(source, leading_newline=False))
1767 MarkupRenderer.plain(source, leading_newline=False))
1768
1768
1769 elif renderer == 'rst':
1769 elif renderer == 'rst':
1770 if repo_name:
1770 if repo_name:
1771 # process patterns on comments if we pass in repo name
1771 # process patterns on comments if we pass in repo name
1772 source, issues = process_patterns(
1772 source, issues = process_patterns(
1773 source, repo_name, link_format='rst')
1773 source, repo_name, link_format='rst')
1774
1774
1775 return literal(
1775 return literal(
1776 '<div class="rst-block">%s</div>' %
1776 '<div class="rst-block">%s</div>' %
1777 maybe_convert_relative_links(
1777 maybe_convert_relative_links(
1778 MarkupRenderer.rst(source, mentions=mentions)))
1778 MarkupRenderer.rst(source, mentions=mentions)))
1779
1779
1780 elif renderer == 'markdown':
1780 elif renderer == 'markdown':
1781 if repo_name:
1781 if repo_name:
1782 # process patterns on comments if we pass in repo name
1782 # process patterns on comments if we pass in repo name
1783 source, issues = process_patterns(
1783 source, issues = process_patterns(
1784 source, repo_name, link_format='markdown')
1784 source, repo_name, link_format='markdown')
1785
1785
1786 return literal(
1786 return literal(
1787 '<div class="markdown-block">%s</div>' %
1787 '<div class="markdown-block">%s</div>' %
1788 maybe_convert_relative_links(
1788 maybe_convert_relative_links(
1789 MarkupRenderer.markdown(source, flavored=True,
1789 MarkupRenderer.markdown(source, flavored=True,
1790 mentions=mentions)))
1790 mentions=mentions)))
1791
1791
1792 elif renderer == 'jupyter':
1792 elif renderer == 'jupyter':
1793 return literal(
1793 return literal(
1794 '<div class="ipynb">%s</div>' %
1794 '<div class="ipynb">%s</div>' %
1795 maybe_convert_relative_links(
1795 maybe_convert_relative_links(
1796 MarkupRenderer.jupyter(source)))
1796 MarkupRenderer.jupyter(source)))
1797
1797
1798 # None means just show the file-source
1798 # None means just show the file-source
1799 return None
1799 return None
1800
1800
1801
1801
1802 def commit_status(repo, commit_id):
1802 def commit_status(repo, commit_id):
1803 return ChangesetStatusModel().get_status(repo, commit_id)
1803 return ChangesetStatusModel().get_status(repo, commit_id)
1804
1804
1805
1805
1806 def commit_status_lbl(commit_status):
1806 def commit_status_lbl(commit_status):
1807 return dict(ChangesetStatus.STATUSES).get(commit_status)
1807 return dict(ChangesetStatus.STATUSES).get(commit_status)
1808
1808
1809
1809
1810 def commit_time(repo_name, commit_id):
1810 def commit_time(repo_name, commit_id):
1811 repo = Repository.get_by_repo_name(repo_name)
1811 repo = Repository.get_by_repo_name(repo_name)
1812 commit = repo.get_commit(commit_id=commit_id)
1812 commit = repo.get_commit(commit_id=commit_id)
1813 return commit.date
1813 return commit.date
1814
1814
1815
1815
1816 def get_permission_name(key):
1816 def get_permission_name(key):
1817 return dict(Permission.PERMS).get(key)
1817 return dict(Permission.PERMS).get(key)
1818
1818
1819
1819
1820 def journal_filter_help(request):
1820 def journal_filter_help(request):
1821 _ = request.translate
1821 _ = request.translate
1822 from rhodecode.lib.audit_logger import ACTIONS
1822 from rhodecode.lib.audit_logger import ACTIONS
1823 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1823 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1824
1824
1825 return _(
1825 return _(
1826 'Example filter terms:\n' +
1826 'Example filter terms:\n' +
1827 ' repository:vcs\n' +
1827 ' repository:vcs\n' +
1828 ' username:marcin\n' +
1828 ' username:marcin\n' +
1829 ' username:(NOT marcin)\n' +
1829 ' username:(NOT marcin)\n' +
1830 ' action:*push*\n' +
1830 ' action:*push*\n' +
1831 ' ip:127.0.0.1\n' +
1831 ' ip:127.0.0.1\n' +
1832 ' date:20120101\n' +
1832 ' date:20120101\n' +
1833 ' date:[20120101100000 TO 20120102]\n' +
1833 ' date:[20120101100000 TO 20120102]\n' +
1834 '\n' +
1834 '\n' +
1835 'Actions: {actions}\n' +
1835 'Actions: {actions}\n' +
1836 '\n' +
1836 '\n' +
1837 'Generate wildcards using \'*\' character:\n' +
1837 'Generate wildcards using \'*\' character:\n' +
1838 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1838 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1839 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1839 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1840 '\n' +
1840 '\n' +
1841 'Optional AND / OR operators in queries\n' +
1841 'Optional AND / OR operators in queries\n' +
1842 ' "repository:vcs OR repository:test"\n' +
1842 ' "repository:vcs OR repository:test"\n' +
1843 ' "username:test AND repository:test*"\n'
1843 ' "username:test AND repository:test*"\n'
1844 ).format(actions=actions)
1844 ).format(actions=actions)
1845
1845
1846
1846
1847 def not_mapped_error(repo_name):
1847 def not_mapped_error(repo_name):
1848 from rhodecode.translation import _
1848 from rhodecode.translation import _
1849 flash(_('%s repository is not mapped to db perhaps'
1849 flash(_('%s repository is not mapped to db perhaps'
1850 ' it was created or renamed from the filesystem'
1850 ' it was created or renamed from the filesystem'
1851 ' please run the application again'
1851 ' please run the application again'
1852 ' in order to rescan repositories') % repo_name, category='error')
1852 ' in order to rescan repositories') % repo_name, category='error')
1853
1853
1854
1854
1855 def ip_range(ip_addr):
1855 def ip_range(ip_addr):
1856 from rhodecode.model.db import UserIpMap
1856 from rhodecode.model.db import UserIpMap
1857 s, e = UserIpMap._get_ip_range(ip_addr)
1857 s, e = UserIpMap._get_ip_range(ip_addr)
1858 return '%s - %s' % (s, e)
1858 return '%s - %s' % (s, e)
1859
1859
1860
1860
1861 def form(url, method='post', needs_csrf_token=True, **attrs):
1861 def form(url, method='post', needs_csrf_token=True, **attrs):
1862 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1862 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1863 if method.lower() != 'get' and needs_csrf_token:
1863 if method.lower() != 'get' and needs_csrf_token:
1864 raise Exception(
1864 raise Exception(
1865 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1865 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1866 'CSRF token. If the endpoint does not require such token you can ' +
1866 'CSRF token. If the endpoint does not require such token you can ' +
1867 'explicitly set the parameter needs_csrf_token to false.')
1867 'explicitly set the parameter needs_csrf_token to false.')
1868
1868
1869 return wh_form(url, method=method, **attrs)
1869 return wh_form(url, method=method, **attrs)
1870
1870
1871
1871
1872 def secure_form(form_url, method="POST", multipart=False, **attrs):
1872 def secure_form(form_url, method="POST", multipart=False, **attrs):
1873 """Start a form tag that points the action to an url. This
1873 """Start a form tag that points the action to an url. This
1874 form tag will also include the hidden field containing
1874 form tag will also include the hidden field containing
1875 the auth token.
1875 the auth token.
1876
1876
1877 The url options should be given either as a string, or as a
1877 The url options should be given either as a string, or as a
1878 ``url()`` function. The method for the form defaults to POST.
1878 ``url()`` function. The method for the form defaults to POST.
1879
1879
1880 Options:
1880 Options:
1881
1881
1882 ``multipart``
1882 ``multipart``
1883 If set to True, the enctype is set to "multipart/form-data".
1883 If set to True, the enctype is set to "multipart/form-data".
1884 ``method``
1884 ``method``
1885 The method to use when submitting the form, usually either
1885 The method to use when submitting the form, usually either
1886 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1886 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1887 hidden input with name _method is added to simulate the verb
1887 hidden input with name _method is added to simulate the verb
1888 over POST.
1888 over POST.
1889
1889
1890 """
1890 """
1891 from webhelpers.pylonslib.secure_form import insecure_form
1891 from webhelpers.pylonslib.secure_form import insecure_form
1892
1892
1893 if 'request' in attrs:
1893 if 'request' in attrs:
1894 session = attrs['request'].session
1894 session = attrs['request'].session
1895 del attrs['request']
1895 del attrs['request']
1896 else:
1896 else:
1897 raise ValueError(
1897 raise ValueError(
1898 'Calling this form requires request= to be passed as argument')
1898 'Calling this form requires request= to be passed as argument')
1899
1899
1900 form = insecure_form(form_url, method, multipart, **attrs)
1900 form = insecure_form(form_url, method, multipart, **attrs)
1901 token = literal(
1901 token = literal(
1902 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1902 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1903 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1903 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1904
1904
1905 return literal("%s\n%s" % (form, token))
1905 return literal("%s\n%s" % (form, token))
1906
1906
1907
1907
1908 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1908 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1909 select_html = select(name, selected, options, **attrs)
1909 select_html = select(name, selected, options, **attrs)
1910 select2 = """
1910 select2 = """
1911 <script>
1911 <script>
1912 $(document).ready(function() {
1912 $(document).ready(function() {
1913 $('#%s').select2({
1913 $('#%s').select2({
1914 containerCssClass: 'drop-menu',
1914 containerCssClass: 'drop-menu',
1915 dropdownCssClass: 'drop-menu-dropdown',
1915 dropdownCssClass: 'drop-menu-dropdown',
1916 dropdownAutoWidth: true%s
1916 dropdownAutoWidth: true%s
1917 });
1917 });
1918 });
1918 });
1919 </script>
1919 </script>
1920 """
1920 """
1921 filter_option = """,
1921 filter_option = """,
1922 minimumResultsForSearch: -1
1922 minimumResultsForSearch: -1
1923 """
1923 """
1924 input_id = attrs.get('id') or name
1924 input_id = attrs.get('id') or name
1925 filter_enabled = "" if enable_filter else filter_option
1925 filter_enabled = "" if enable_filter else filter_option
1926 select_script = literal(select2 % (input_id, filter_enabled))
1926 select_script = literal(select2 % (input_id, filter_enabled))
1927
1927
1928 return literal(select_html+select_script)
1928 return literal(select_html+select_script)
1929
1929
1930
1930
1931 def get_visual_attr(tmpl_context_var, attr_name):
1931 def get_visual_attr(tmpl_context_var, attr_name):
1932 """
1932 """
1933 A safe way to get a variable from visual variable of template context
1933 A safe way to get a variable from visual variable of template context
1934
1934
1935 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1935 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1936 :param attr_name: name of the attribute we fetch from the c.visual
1936 :param attr_name: name of the attribute we fetch from the c.visual
1937 """
1937 """
1938 visual = getattr(tmpl_context_var, 'visual', None)
1938 visual = getattr(tmpl_context_var, 'visual', None)
1939 if not visual:
1939 if not visual:
1940 return
1940 return
1941 else:
1941 else:
1942 return getattr(visual, attr_name, None)
1942 return getattr(visual, attr_name, None)
1943
1943
1944
1944
1945 def get_last_path_part(file_node):
1945 def get_last_path_part(file_node):
1946 if not file_node.path:
1946 if not file_node.path:
1947 return u''
1947 return u''
1948
1948
1949 path = safe_unicode(file_node.path.split('/')[-1])
1949 path = safe_unicode(file_node.path.split('/')[-1])
1950 return u'../' + path
1950 return u'../' + path
1951
1951
1952
1952
1953 def route_url(*args, **kwargs):
1953 def route_url(*args, **kwargs):
1954 """
1954 """
1955 Wrapper around pyramids `route_url` (fully qualified url) function.
1955 Wrapper around pyramids `route_url` (fully qualified url) function.
1956 """
1956 """
1957 req = get_current_request()
1957 req = get_current_request()
1958 return req.route_url(*args, **kwargs)
1958 return req.route_url(*args, **kwargs)
1959
1959
1960
1960
1961 def route_path(*args, **kwargs):
1961 def route_path(*args, **kwargs):
1962 """
1962 """
1963 Wrapper around pyramids `route_path` function.
1963 Wrapper around pyramids `route_path` function.
1964 """
1964 """
1965 req = get_current_request()
1965 req = get_current_request()
1966 return req.route_path(*args, **kwargs)
1966 return req.route_path(*args, **kwargs)
1967
1967
1968
1968
1969 def route_path_or_none(*args, **kwargs):
1969 def route_path_or_none(*args, **kwargs):
1970 try:
1970 try:
1971 return route_path(*args, **kwargs)
1971 return route_path(*args, **kwargs)
1972 except KeyError:
1972 except KeyError:
1973 return None
1973 return None
1974
1974
1975
1975
1976 def current_route_path(request, **kw):
1976 def current_route_path(request, **kw):
1977 new_args = request.GET.mixed()
1977 new_args = request.GET.mixed()
1978 new_args.update(kw)
1978 new_args.update(kw)
1979 return request.current_route_path(_query=new_args)
1979 return request.current_route_path(_query=new_args)
1980
1980
1981
1981
1982 def api_call_example(method, args):
1982 def api_call_example(method, args):
1983 """
1983 """
1984 Generates an API call example via CURL
1984 Generates an API call example via CURL
1985 """
1985 """
1986 args_json = json.dumps(OrderedDict([
1986 args_json = json.dumps(OrderedDict([
1987 ('id', 1),
1987 ('id', 1),
1988 ('auth_token', 'SECRET'),
1988 ('auth_token', 'SECRET'),
1989 ('method', method),
1989 ('method', method),
1990 ('args', args)
1990 ('args', args)
1991 ]))
1991 ]))
1992 return literal(
1992 return literal(
1993 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1993 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1994 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1994 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1995 "and needs to be of `api calls` role."
1995 "and needs to be of `api calls` role."
1996 .format(
1996 .format(
1997 api_url=route_url('apiv2'),
1997 api_url=route_url('apiv2'),
1998 token_url=route_url('my_account_auth_tokens'),
1998 token_url=route_url('my_account_auth_tokens'),
1999 data=args_json))
1999 data=args_json))
2000
2000
2001
2001
2002 def notification_description(notification, request):
2002 def notification_description(notification, request):
2003 """
2003 """
2004 Generate notification human readable description based on notification type
2004 Generate notification human readable description based on notification type
2005 """
2005 """
2006 from rhodecode.model.notification import NotificationModel
2006 from rhodecode.model.notification import NotificationModel
2007 return NotificationModel().make_description(
2007 return NotificationModel().make_description(
2008 notification, translate=request.translate)
2008 notification, translate=request.translate)
2009
2009
2010
2010
2011 def go_import_header(request, db_repo=None):
2011 def go_import_header(request, db_repo=None):
2012 """
2012 """
2013 Creates a header for go-import functionality in Go Lang
2013 Creates a header for go-import functionality in Go Lang
2014 """
2014 """
2015
2015
2016 if not db_repo:
2016 if not db_repo:
2017 return
2017 return
2018 if 'go-get' not in request.GET:
2018 if 'go-get' not in request.GET:
2019 return
2019 return
2020
2020
2021 clone_url = db_repo.clone_url()
2021 clone_url = db_repo.clone_url()
2022 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2022 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2023 # we have a repo and go-get flag,
2023 # we have a repo and go-get flag,
2024 return literal('<meta name="go-import" content="{} {} {}">'.format(
2024 return literal('<meta name="go-import" content="{} {} {}">'.format(
2025 prefix, db_repo.repo_type, clone_url))
2025 prefix, db_repo.repo_type, clone_url))
2026
2026
2027
2027
2028 def reviewer_as_json(*args, **kwargs):
2028 def reviewer_as_json(*args, **kwargs):
2029 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2029 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2030 return _reviewer_as_json(*args, **kwargs)
2030 return _reviewer_as_json(*args, **kwargs)
2031
2031
2032
2032
2033 def get_repo_view_type(request):
2033 def get_repo_view_type(request):
2034 route_name = request.matched_route.name
2034 route_name = request.matched_route.name
2035 route_to_view_type = {
2035 route_to_view_type = {
2036 'repo_changelog': 'changelog',
2036 'repo_changelog': 'changelog',
2037 'repo_files': 'files',
2037 'repo_files': 'files',
2038 'repo_summary': 'summary',
2038 'repo_summary': 'summary',
2039 'repo_commit': 'commit'
2039 'repo_commit': 'commit'
2040 }
2040
2041
2041 }
2042 return route_to_view_type.get(route_name)
2042 return route_to_view_type.get(route_name)
@@ -1,4981 +1,4981 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 hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false, case
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from zope.cachedescriptors.property import Lazy as LazyProperty
50 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from pyramid import compat
51 from pyramid import compat
52 from pyramid.threadlocal import get_current_request
52 from pyramid.threadlocal import get_current_request
53
53
54 from rhodecode.translation import _
54 from rhodecode.translation import _
55 from rhodecode.lib.vcs import get_vcs_instance
55 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
56 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.utils2 import (
57 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
58 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 glob2re, StrictAttributeDict, cleaned_uri)
60 glob2re, StrictAttributeDict, cleaned_uri)
61 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
61 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 JsonRaw
62 JsonRaw
63 from rhodecode.lib.ext_json import json
63 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.caching_query import FromCache
64 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
65 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
66 from rhodecode.lib.encrypt2 import Encryptor
66 from rhodecode.lib.encrypt2 import Encryptor
67 from rhodecode.model.meta import Base, Session
67 from rhodecode.model.meta import Base, Session
68
68
69 URL_SEP = '/'
69 URL_SEP = '/'
70 log = logging.getLogger(__name__)
70 log = logging.getLogger(__name__)
71
71
72 # =============================================================================
72 # =============================================================================
73 # BASE CLASSES
73 # BASE CLASSES
74 # =============================================================================
74 # =============================================================================
75
75
76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # beaker.session.secret if first is not set.
77 # beaker.session.secret if first is not set.
78 # and initialized at environment.py
78 # and initialized at environment.py
79 ENCRYPTION_KEY = None
79 ENCRYPTION_KEY = None
80
80
81 # used to sort permissions by types, '#' used here is not allowed to be in
81 # used to sort permissions by types, '#' used here is not allowed to be in
82 # usernames, and it's very early in sorted string.printable table.
82 # usernames, and it's very early in sorted string.printable table.
83 PERMISSION_TYPE_SORT = {
83 PERMISSION_TYPE_SORT = {
84 'admin': '####',
84 'admin': '####',
85 'write': '###',
85 'write': '###',
86 'read': '##',
86 'read': '##',
87 'none': '#',
87 'none': '#',
88 }
88 }
89
89
90
90
91 def display_user_sort(obj):
91 def display_user_sort(obj):
92 """
92 """
93 Sort function used to sort permissions in .permissions() function of
93 Sort function used to sort permissions in .permissions() function of
94 Repository, RepoGroup, UserGroup. Also it put the default user in front
94 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 of all other resources
95 of all other resources
96 """
96 """
97
97
98 if obj.username == User.DEFAULT_USER:
98 if obj.username == User.DEFAULT_USER:
99 return '#####'
99 return '#####'
100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 return prefix + obj.username
101 return prefix + obj.username
102
102
103
103
104 def display_user_group_sort(obj):
104 def display_user_group_sort(obj):
105 """
105 """
106 Sort function used to sort permissions in .permissions() function of
106 Sort function used to sort permissions in .permissions() function of
107 Repository, RepoGroup, UserGroup. Also it put the default user in front
107 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 of all other resources
108 of all other resources
109 """
109 """
110
110
111 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
111 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 return prefix + obj.users_group_name
112 return prefix + obj.users_group_name
113
113
114
114
115 def _hash_key(k):
115 def _hash_key(k):
116 return sha1_safe(k)
116 return sha1_safe(k)
117
117
118
118
119 def in_filter_generator(qry, items, limit=500):
119 def in_filter_generator(qry, items, limit=500):
120 """
120 """
121 Splits IN() into multiple with OR
121 Splits IN() into multiple with OR
122 e.g.::
122 e.g.::
123 cnt = Repository.query().filter(
123 cnt = Repository.query().filter(
124 or_(
124 or_(
125 *in_filter_generator(Repository.repo_id, range(100000))
125 *in_filter_generator(Repository.repo_id, range(100000))
126 )).count()
126 )).count()
127 """
127 """
128 if not items:
128 if not items:
129 # empty list will cause empty query which might cause security issues
129 # empty list will cause empty query which might cause security issues
130 # this can lead to hidden unpleasant results
130 # this can lead to hidden unpleasant results
131 items = [-1]
131 items = [-1]
132
132
133 parts = []
133 parts = []
134 for chunk in xrange(0, len(items), limit):
134 for chunk in xrange(0, len(items), limit):
135 parts.append(
135 parts.append(
136 qry.in_(items[chunk: chunk + limit])
136 qry.in_(items[chunk: chunk + limit])
137 )
137 )
138
138
139 return parts
139 return parts
140
140
141
141
142 base_table_args = {
142 base_table_args = {
143 'extend_existing': True,
143 'extend_existing': True,
144 'mysql_engine': 'InnoDB',
144 'mysql_engine': 'InnoDB',
145 'mysql_charset': 'utf8',
145 'mysql_charset': 'utf8',
146 'sqlite_autoincrement': True
146 'sqlite_autoincrement': True
147 }
147 }
148
148
149
149
150 class EncryptedTextValue(TypeDecorator):
150 class EncryptedTextValue(TypeDecorator):
151 """
151 """
152 Special column for encrypted long text data, use like::
152 Special column for encrypted long text data, use like::
153
153
154 value = Column("encrypted_value", EncryptedValue(), nullable=False)
154 value = Column("encrypted_value", EncryptedValue(), nullable=False)
155
155
156 This column is intelligent so if value is in unencrypted form it return
156 This column is intelligent so if value is in unencrypted form it return
157 unencrypted form, but on save it always encrypts
157 unencrypted form, but on save it always encrypts
158 """
158 """
159 impl = Text
159 impl = Text
160
160
161 def process_bind_param(self, value, dialect):
161 def process_bind_param(self, value, dialect):
162 """
162 """
163 Setter for storing value
163 Setter for storing value
164 """
164 """
165 import rhodecode
165 import rhodecode
166 if not value:
166 if not value:
167 return value
167 return value
168
168
169 # protect against double encrypting if values is already encrypted
169 # protect against double encrypting if values is already encrypted
170 if value.startswith('enc$aes$') \
170 if value.startswith('enc$aes$') \
171 or value.startswith('enc$aes_hmac$') \
171 or value.startswith('enc$aes_hmac$') \
172 or value.startswith('enc2$'):
172 or value.startswith('enc2$'):
173 raise ValueError('value needs to be in unencrypted format, '
173 raise ValueError('value needs to be in unencrypted format, '
174 'ie. not starting with enc$ or enc2$')
174 'ie. not starting with enc$ or enc2$')
175
175
176 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
176 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
177 if algo == 'aes':
177 if algo == 'aes':
178 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
178 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
179 elif algo == 'fernet':
179 elif algo == 'fernet':
180 return Encryptor(ENCRYPTION_KEY).encrypt(value)
180 return Encryptor(ENCRYPTION_KEY).encrypt(value)
181 else:
181 else:
182 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
182 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
183
183
184 def process_result_value(self, value, dialect):
184 def process_result_value(self, value, dialect):
185 """
185 """
186 Getter for retrieving value
186 Getter for retrieving value
187 """
187 """
188
188
189 import rhodecode
189 import rhodecode
190 if not value:
190 if not value:
191 return value
191 return value
192
192
193 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
193 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
194 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
194 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
195 if algo == 'aes':
195 if algo == 'aes':
196 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
196 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
197 elif algo == 'fernet':
197 elif algo == 'fernet':
198 return Encryptor(ENCRYPTION_KEY).decrypt(value)
198 return Encryptor(ENCRYPTION_KEY).decrypt(value)
199 else:
199 else:
200 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
200 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
201 return decrypted_data
201 return decrypted_data
202
202
203
203
204 class BaseModel(object):
204 class BaseModel(object):
205 """
205 """
206 Base Model for all classes
206 Base Model for all classes
207 """
207 """
208
208
209 @classmethod
209 @classmethod
210 def _get_keys(cls):
210 def _get_keys(cls):
211 """return column names for this model """
211 """return column names for this model """
212 return class_mapper(cls).c.keys()
212 return class_mapper(cls).c.keys()
213
213
214 def get_dict(self):
214 def get_dict(self):
215 """
215 """
216 return dict with keys and values corresponding
216 return dict with keys and values corresponding
217 to this model data """
217 to this model data """
218
218
219 d = {}
219 d = {}
220 for k in self._get_keys():
220 for k in self._get_keys():
221 d[k] = getattr(self, k)
221 d[k] = getattr(self, k)
222
222
223 # also use __json__() if present to get additional fields
223 # also use __json__() if present to get additional fields
224 _json_attr = getattr(self, '__json__', None)
224 _json_attr = getattr(self, '__json__', None)
225 if _json_attr:
225 if _json_attr:
226 # update with attributes from __json__
226 # update with attributes from __json__
227 if callable(_json_attr):
227 if callable(_json_attr):
228 _json_attr = _json_attr()
228 _json_attr = _json_attr()
229 for k, val in _json_attr.iteritems():
229 for k, val in _json_attr.iteritems():
230 d[k] = val
230 d[k] = val
231 return d
231 return d
232
232
233 def get_appstruct(self):
233 def get_appstruct(self):
234 """return list with keys and values tuples corresponding
234 """return list with keys and values tuples corresponding
235 to this model data """
235 to this model data """
236
236
237 lst = []
237 lst = []
238 for k in self._get_keys():
238 for k in self._get_keys():
239 lst.append((k, getattr(self, k),))
239 lst.append((k, getattr(self, k),))
240 return lst
240 return lst
241
241
242 def populate_obj(self, populate_dict):
242 def populate_obj(self, populate_dict):
243 """populate model with data from given populate_dict"""
243 """populate model with data from given populate_dict"""
244
244
245 for k in self._get_keys():
245 for k in self._get_keys():
246 if k in populate_dict:
246 if k in populate_dict:
247 setattr(self, k, populate_dict[k])
247 setattr(self, k, populate_dict[k])
248
248
249 @classmethod
249 @classmethod
250 def query(cls):
250 def query(cls):
251 return Session().query(cls)
251 return Session().query(cls)
252
252
253 @classmethod
253 @classmethod
254 def get(cls, id_):
254 def get(cls, id_):
255 if id_:
255 if id_:
256 return cls.query().get(id_)
256 return cls.query().get(id_)
257
257
258 @classmethod
258 @classmethod
259 def get_or_404(cls, id_):
259 def get_or_404(cls, id_):
260 from pyramid.httpexceptions import HTTPNotFound
260 from pyramid.httpexceptions import HTTPNotFound
261
261
262 try:
262 try:
263 id_ = int(id_)
263 id_ = int(id_)
264 except (TypeError, ValueError):
264 except (TypeError, ValueError):
265 raise HTTPNotFound()
265 raise HTTPNotFound()
266
266
267 res = cls.query().get(id_)
267 res = cls.query().get(id_)
268 if not res:
268 if not res:
269 raise HTTPNotFound()
269 raise HTTPNotFound()
270 return res
270 return res
271
271
272 @classmethod
272 @classmethod
273 def getAll(cls):
273 def getAll(cls):
274 # deprecated and left for backward compatibility
274 # deprecated and left for backward compatibility
275 return cls.get_all()
275 return cls.get_all()
276
276
277 @classmethod
277 @classmethod
278 def get_all(cls):
278 def get_all(cls):
279 return cls.query().all()
279 return cls.query().all()
280
280
281 @classmethod
281 @classmethod
282 def delete(cls, id_):
282 def delete(cls, id_):
283 obj = cls.query().get(id_)
283 obj = cls.query().get(id_)
284 Session().delete(obj)
284 Session().delete(obj)
285
285
286 @classmethod
286 @classmethod
287 def identity_cache(cls, session, attr_name, value):
287 def identity_cache(cls, session, attr_name, value):
288 exist_in_session = []
288 exist_in_session = []
289 for (item_cls, pkey), instance in session.identity_map.items():
289 for (item_cls, pkey), instance in session.identity_map.items():
290 if cls == item_cls and getattr(instance, attr_name) == value:
290 if cls == item_cls and getattr(instance, attr_name) == value:
291 exist_in_session.append(instance)
291 exist_in_session.append(instance)
292 if exist_in_session:
292 if exist_in_session:
293 if len(exist_in_session) == 1:
293 if len(exist_in_session) == 1:
294 return exist_in_session[0]
294 return exist_in_session[0]
295 log.exception(
295 log.exception(
296 'multiple objects with attr %s and '
296 'multiple objects with attr %s and '
297 'value %s found with same name: %r',
297 'value %s found with same name: %r',
298 attr_name, value, exist_in_session)
298 attr_name, value, exist_in_session)
299
299
300 def __repr__(self):
300 def __repr__(self):
301 if hasattr(self, '__unicode__'):
301 if hasattr(self, '__unicode__'):
302 # python repr needs to return str
302 # python repr needs to return str
303 try:
303 try:
304 return safe_str(self.__unicode__())
304 return safe_str(self.__unicode__())
305 except UnicodeDecodeError:
305 except UnicodeDecodeError:
306 pass
306 pass
307 return '<DB:%s>' % (self.__class__.__name__)
307 return '<DB:%s>' % (self.__class__.__name__)
308
308
309
309
310 class RhodeCodeSetting(Base, BaseModel):
310 class RhodeCodeSetting(Base, BaseModel):
311 __tablename__ = 'rhodecode_settings'
311 __tablename__ = 'rhodecode_settings'
312 __table_args__ = (
312 __table_args__ = (
313 UniqueConstraint('app_settings_name'),
313 UniqueConstraint('app_settings_name'),
314 base_table_args
314 base_table_args
315 )
315 )
316
316
317 SETTINGS_TYPES = {
317 SETTINGS_TYPES = {
318 'str': safe_str,
318 'str': safe_str,
319 'int': safe_int,
319 'int': safe_int,
320 'unicode': safe_unicode,
320 'unicode': safe_unicode,
321 'bool': str2bool,
321 'bool': str2bool,
322 'list': functools.partial(aslist, sep=',')
322 'list': functools.partial(aslist, sep=',')
323 }
323 }
324 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
324 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
325 GLOBAL_CONF_KEY = 'app_settings'
325 GLOBAL_CONF_KEY = 'app_settings'
326
326
327 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
327 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
328 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
328 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
329 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
329 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
330 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
330 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
331
331
332 def __init__(self, key='', val='', type='unicode'):
332 def __init__(self, key='', val='', type='unicode'):
333 self.app_settings_name = key
333 self.app_settings_name = key
334 self.app_settings_type = type
334 self.app_settings_type = type
335 self.app_settings_value = val
335 self.app_settings_value = val
336
336
337 @validates('_app_settings_value')
337 @validates('_app_settings_value')
338 def validate_settings_value(self, key, val):
338 def validate_settings_value(self, key, val):
339 assert type(val) == unicode
339 assert type(val) == unicode
340 return val
340 return val
341
341
342 @hybrid_property
342 @hybrid_property
343 def app_settings_value(self):
343 def app_settings_value(self):
344 v = self._app_settings_value
344 v = self._app_settings_value
345 _type = self.app_settings_type
345 _type = self.app_settings_type
346 if _type:
346 if _type:
347 _type = self.app_settings_type.split('.')[0]
347 _type = self.app_settings_type.split('.')[0]
348 # decode the encrypted value
348 # decode the encrypted value
349 if 'encrypted' in self.app_settings_type:
349 if 'encrypted' in self.app_settings_type:
350 cipher = EncryptedTextValue()
350 cipher = EncryptedTextValue()
351 v = safe_unicode(cipher.process_result_value(v, None))
351 v = safe_unicode(cipher.process_result_value(v, None))
352
352
353 converter = self.SETTINGS_TYPES.get(_type) or \
353 converter = self.SETTINGS_TYPES.get(_type) or \
354 self.SETTINGS_TYPES['unicode']
354 self.SETTINGS_TYPES['unicode']
355 return converter(v)
355 return converter(v)
356
356
357 @app_settings_value.setter
357 @app_settings_value.setter
358 def app_settings_value(self, val):
358 def app_settings_value(self, val):
359 """
359 """
360 Setter that will always make sure we use unicode in app_settings_value
360 Setter that will always make sure we use unicode in app_settings_value
361
361
362 :param val:
362 :param val:
363 """
363 """
364 val = safe_unicode(val)
364 val = safe_unicode(val)
365 # encode the encrypted value
365 # encode the encrypted value
366 if 'encrypted' in self.app_settings_type:
366 if 'encrypted' in self.app_settings_type:
367 cipher = EncryptedTextValue()
367 cipher = EncryptedTextValue()
368 val = safe_unicode(cipher.process_bind_param(val, None))
368 val = safe_unicode(cipher.process_bind_param(val, None))
369 self._app_settings_value = val
369 self._app_settings_value = val
370
370
371 @hybrid_property
371 @hybrid_property
372 def app_settings_type(self):
372 def app_settings_type(self):
373 return self._app_settings_type
373 return self._app_settings_type
374
374
375 @app_settings_type.setter
375 @app_settings_type.setter
376 def app_settings_type(self, val):
376 def app_settings_type(self, val):
377 if val.split('.')[0] not in self.SETTINGS_TYPES:
377 if val.split('.')[0] not in self.SETTINGS_TYPES:
378 raise Exception('type must be one of %s got %s'
378 raise Exception('type must be one of %s got %s'
379 % (self.SETTINGS_TYPES.keys(), val))
379 % (self.SETTINGS_TYPES.keys(), val))
380 self._app_settings_type = val
380 self._app_settings_type = val
381
381
382 @classmethod
382 @classmethod
383 def get_by_prefix(cls, prefix):
383 def get_by_prefix(cls, prefix):
384 return RhodeCodeSetting.query()\
384 return RhodeCodeSetting.query()\
385 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
385 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
386 .all()
386 .all()
387
387
388 def __unicode__(self):
388 def __unicode__(self):
389 return u"<%s('%s:%s[%s]')>" % (
389 return u"<%s('%s:%s[%s]')>" % (
390 self.__class__.__name__,
390 self.__class__.__name__,
391 self.app_settings_name, self.app_settings_value,
391 self.app_settings_name, self.app_settings_value,
392 self.app_settings_type
392 self.app_settings_type
393 )
393 )
394
394
395
395
396 class RhodeCodeUi(Base, BaseModel):
396 class RhodeCodeUi(Base, BaseModel):
397 __tablename__ = 'rhodecode_ui'
397 __tablename__ = 'rhodecode_ui'
398 __table_args__ = (
398 __table_args__ = (
399 UniqueConstraint('ui_key'),
399 UniqueConstraint('ui_key'),
400 base_table_args
400 base_table_args
401 )
401 )
402
402
403 HOOK_REPO_SIZE = 'changegroup.repo_size'
403 HOOK_REPO_SIZE = 'changegroup.repo_size'
404 # HG
404 # HG
405 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
405 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
406 HOOK_PULL = 'outgoing.pull_logger'
406 HOOK_PULL = 'outgoing.pull_logger'
407 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
407 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
408 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
408 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
409 HOOK_PUSH = 'changegroup.push_logger'
409 HOOK_PUSH = 'changegroup.push_logger'
410 HOOK_PUSH_KEY = 'pushkey.key_push'
410 HOOK_PUSH_KEY = 'pushkey.key_push'
411
411
412 # TODO: johbo: Unify way how hooks are configured for git and hg,
412 # TODO: johbo: Unify way how hooks are configured for git and hg,
413 # git part is currently hardcoded.
413 # git part is currently hardcoded.
414
414
415 # SVN PATTERNS
415 # SVN PATTERNS
416 SVN_BRANCH_ID = 'vcs_svn_branch'
416 SVN_BRANCH_ID = 'vcs_svn_branch'
417 SVN_TAG_ID = 'vcs_svn_tag'
417 SVN_TAG_ID = 'vcs_svn_tag'
418
418
419 ui_id = Column(
419 ui_id = Column(
420 "ui_id", Integer(), nullable=False, unique=True, default=None,
420 "ui_id", Integer(), nullable=False, unique=True, default=None,
421 primary_key=True)
421 primary_key=True)
422 ui_section = Column(
422 ui_section = Column(
423 "ui_section", String(255), nullable=True, unique=None, default=None)
423 "ui_section", String(255), nullable=True, unique=None, default=None)
424 ui_key = Column(
424 ui_key = Column(
425 "ui_key", String(255), nullable=True, unique=None, default=None)
425 "ui_key", String(255), nullable=True, unique=None, default=None)
426 ui_value = Column(
426 ui_value = Column(
427 "ui_value", String(255), nullable=True, unique=None, default=None)
427 "ui_value", String(255), nullable=True, unique=None, default=None)
428 ui_active = Column(
428 ui_active = Column(
429 "ui_active", Boolean(), nullable=True, unique=None, default=True)
429 "ui_active", Boolean(), nullable=True, unique=None, default=True)
430
430
431 def __repr__(self):
431 def __repr__(self):
432 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
432 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
433 self.ui_key, self.ui_value)
433 self.ui_key, self.ui_value)
434
434
435
435
436 class RepoRhodeCodeSetting(Base, BaseModel):
436 class RepoRhodeCodeSetting(Base, BaseModel):
437 __tablename__ = 'repo_rhodecode_settings'
437 __tablename__ = 'repo_rhodecode_settings'
438 __table_args__ = (
438 __table_args__ = (
439 UniqueConstraint(
439 UniqueConstraint(
440 'app_settings_name', 'repository_id',
440 'app_settings_name', 'repository_id',
441 name='uq_repo_rhodecode_setting_name_repo_id'),
441 name='uq_repo_rhodecode_setting_name_repo_id'),
442 base_table_args
442 base_table_args
443 )
443 )
444
444
445 repository_id = Column(
445 repository_id = Column(
446 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
446 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
447 nullable=False)
447 nullable=False)
448 app_settings_id = Column(
448 app_settings_id = Column(
449 "app_settings_id", Integer(), nullable=False, unique=True,
449 "app_settings_id", Integer(), nullable=False, unique=True,
450 default=None, primary_key=True)
450 default=None, primary_key=True)
451 app_settings_name = Column(
451 app_settings_name = Column(
452 "app_settings_name", String(255), nullable=True, unique=None,
452 "app_settings_name", String(255), nullable=True, unique=None,
453 default=None)
453 default=None)
454 _app_settings_value = Column(
454 _app_settings_value = Column(
455 "app_settings_value", String(4096), nullable=True, unique=None,
455 "app_settings_value", String(4096), nullable=True, unique=None,
456 default=None)
456 default=None)
457 _app_settings_type = Column(
457 _app_settings_type = Column(
458 "app_settings_type", String(255), nullable=True, unique=None,
458 "app_settings_type", String(255), nullable=True, unique=None,
459 default=None)
459 default=None)
460
460
461 repository = relationship('Repository')
461 repository = relationship('Repository')
462
462
463 def __init__(self, repository_id, key='', val='', type='unicode'):
463 def __init__(self, repository_id, key='', val='', type='unicode'):
464 self.repository_id = repository_id
464 self.repository_id = repository_id
465 self.app_settings_name = key
465 self.app_settings_name = key
466 self.app_settings_type = type
466 self.app_settings_type = type
467 self.app_settings_value = val
467 self.app_settings_value = val
468
468
469 @validates('_app_settings_value')
469 @validates('_app_settings_value')
470 def validate_settings_value(self, key, val):
470 def validate_settings_value(self, key, val):
471 assert type(val) == unicode
471 assert type(val) == unicode
472 return val
472 return val
473
473
474 @hybrid_property
474 @hybrid_property
475 def app_settings_value(self):
475 def app_settings_value(self):
476 v = self._app_settings_value
476 v = self._app_settings_value
477 type_ = self.app_settings_type
477 type_ = self.app_settings_type
478 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
478 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
479 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
479 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
480 return converter(v)
480 return converter(v)
481
481
482 @app_settings_value.setter
482 @app_settings_value.setter
483 def app_settings_value(self, val):
483 def app_settings_value(self, val):
484 """
484 """
485 Setter that will always make sure we use unicode in app_settings_value
485 Setter that will always make sure we use unicode in app_settings_value
486
486
487 :param val:
487 :param val:
488 """
488 """
489 self._app_settings_value = safe_unicode(val)
489 self._app_settings_value = safe_unicode(val)
490
490
491 @hybrid_property
491 @hybrid_property
492 def app_settings_type(self):
492 def app_settings_type(self):
493 return self._app_settings_type
493 return self._app_settings_type
494
494
495 @app_settings_type.setter
495 @app_settings_type.setter
496 def app_settings_type(self, val):
496 def app_settings_type(self, val):
497 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
497 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
498 if val not in SETTINGS_TYPES:
498 if val not in SETTINGS_TYPES:
499 raise Exception('type must be one of %s got %s'
499 raise Exception('type must be one of %s got %s'
500 % (SETTINGS_TYPES.keys(), val))
500 % (SETTINGS_TYPES.keys(), val))
501 self._app_settings_type = val
501 self._app_settings_type = val
502
502
503 def __unicode__(self):
503 def __unicode__(self):
504 return u"<%s('%s:%s:%s[%s]')>" % (
504 return u"<%s('%s:%s:%s[%s]')>" % (
505 self.__class__.__name__, self.repository.repo_name,
505 self.__class__.__name__, self.repository.repo_name,
506 self.app_settings_name, self.app_settings_value,
506 self.app_settings_name, self.app_settings_value,
507 self.app_settings_type
507 self.app_settings_type
508 )
508 )
509
509
510
510
511 class RepoRhodeCodeUi(Base, BaseModel):
511 class RepoRhodeCodeUi(Base, BaseModel):
512 __tablename__ = 'repo_rhodecode_ui'
512 __tablename__ = 'repo_rhodecode_ui'
513 __table_args__ = (
513 __table_args__ = (
514 UniqueConstraint(
514 UniqueConstraint(
515 'repository_id', 'ui_section', 'ui_key',
515 'repository_id', 'ui_section', 'ui_key',
516 name='uq_repo_rhodecode_ui_repository_id_section_key'),
516 name='uq_repo_rhodecode_ui_repository_id_section_key'),
517 base_table_args
517 base_table_args
518 )
518 )
519
519
520 repository_id = Column(
520 repository_id = Column(
521 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
521 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
522 nullable=False)
522 nullable=False)
523 ui_id = Column(
523 ui_id = Column(
524 "ui_id", Integer(), nullable=False, unique=True, default=None,
524 "ui_id", Integer(), nullable=False, unique=True, default=None,
525 primary_key=True)
525 primary_key=True)
526 ui_section = Column(
526 ui_section = Column(
527 "ui_section", String(255), nullable=True, unique=None, default=None)
527 "ui_section", String(255), nullable=True, unique=None, default=None)
528 ui_key = Column(
528 ui_key = Column(
529 "ui_key", String(255), nullable=True, unique=None, default=None)
529 "ui_key", String(255), nullable=True, unique=None, default=None)
530 ui_value = Column(
530 ui_value = Column(
531 "ui_value", String(255), nullable=True, unique=None, default=None)
531 "ui_value", String(255), nullable=True, unique=None, default=None)
532 ui_active = Column(
532 ui_active = Column(
533 "ui_active", Boolean(), nullable=True, unique=None, default=True)
533 "ui_active", Boolean(), nullable=True, unique=None, default=True)
534
534
535 repository = relationship('Repository')
535 repository = relationship('Repository')
536
536
537 def __repr__(self):
537 def __repr__(self):
538 return '<%s[%s:%s]%s=>%s]>' % (
538 return '<%s[%s:%s]%s=>%s]>' % (
539 self.__class__.__name__, self.repository.repo_name,
539 self.__class__.__name__, self.repository.repo_name,
540 self.ui_section, self.ui_key, self.ui_value)
540 self.ui_section, self.ui_key, self.ui_value)
541
541
542
542
543 class User(Base, BaseModel):
543 class User(Base, BaseModel):
544 __tablename__ = 'users'
544 __tablename__ = 'users'
545 __table_args__ = (
545 __table_args__ = (
546 UniqueConstraint('username'), UniqueConstraint('email'),
546 UniqueConstraint('username'), UniqueConstraint('email'),
547 Index('u_username_idx', 'username'),
547 Index('u_username_idx', 'username'),
548 Index('u_email_idx', 'email'),
548 Index('u_email_idx', 'email'),
549 base_table_args
549 base_table_args
550 )
550 )
551
551
552 DEFAULT_USER = 'default'
552 DEFAULT_USER = 'default'
553 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
553 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
554 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
554 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
555
555
556 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
556 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
557 username = Column("username", String(255), nullable=True, unique=None, default=None)
557 username = Column("username", String(255), nullable=True, unique=None, default=None)
558 password = Column("password", String(255), nullable=True, unique=None, default=None)
558 password = Column("password", String(255), nullable=True, unique=None, default=None)
559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
560 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
560 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
561 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
561 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
562 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
562 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
563 _email = Column("email", String(255), nullable=True, unique=None, default=None)
563 _email = Column("email", String(255), nullable=True, unique=None, default=None)
564 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
564 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
565 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
565 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
566
566
567 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
567 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
568 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
568 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
569 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
569 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
570 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
570 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
571 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
571 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
572 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
572 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
573
573
574 user_log = relationship('UserLog')
574 user_log = relationship('UserLog')
575 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
575 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
576
576
577 repositories = relationship('Repository')
577 repositories = relationship('Repository')
578 repository_groups = relationship('RepoGroup')
578 repository_groups = relationship('RepoGroup')
579 user_groups = relationship('UserGroup')
579 user_groups = relationship('UserGroup')
580
580
581 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
581 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
582 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
582 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
583
583
584 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
584 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
585 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
585 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
586 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
586 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
587
587
588 group_member = relationship('UserGroupMember', cascade='all')
588 group_member = relationship('UserGroupMember', cascade='all')
589
589
590 notifications = relationship('UserNotification', cascade='all')
590 notifications = relationship('UserNotification', cascade='all')
591 # notifications assigned to this user
591 # notifications assigned to this user
592 user_created_notifications = relationship('Notification', cascade='all')
592 user_created_notifications = relationship('Notification', cascade='all')
593 # comments created by this user
593 # comments created by this user
594 user_comments = relationship('ChangesetComment', cascade='all')
594 user_comments = relationship('ChangesetComment', cascade='all')
595 # user profile extra info
595 # user profile extra info
596 user_emails = relationship('UserEmailMap', cascade='all')
596 user_emails = relationship('UserEmailMap', cascade='all')
597 user_ip_map = relationship('UserIpMap', cascade='all')
597 user_ip_map = relationship('UserIpMap', cascade='all')
598 user_auth_tokens = relationship('UserApiKeys', cascade='all')
598 user_auth_tokens = relationship('UserApiKeys', cascade='all')
599 user_ssh_keys = relationship('UserSshKeys', cascade='all')
599 user_ssh_keys = relationship('UserSshKeys', cascade='all')
600
600
601 # gists
601 # gists
602 user_gists = relationship('Gist', cascade='all')
602 user_gists = relationship('Gist', cascade='all')
603 # user pull requests
603 # user pull requests
604 user_pull_requests = relationship('PullRequest', cascade='all')
604 user_pull_requests = relationship('PullRequest', cascade='all')
605 # external identities
605 # external identities
606 extenal_identities = relationship(
606 extenal_identities = relationship(
607 'ExternalIdentity',
607 'ExternalIdentity',
608 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
608 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
609 cascade='all')
609 cascade='all')
610 # review rules
610 # review rules
611 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
611 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
612
612
613 def __unicode__(self):
613 def __unicode__(self):
614 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
614 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
615 self.user_id, self.username)
615 self.user_id, self.username)
616
616
617 @hybrid_property
617 @hybrid_property
618 def email(self):
618 def email(self):
619 return self._email
619 return self._email
620
620
621 @email.setter
621 @email.setter
622 def email(self, val):
622 def email(self, val):
623 self._email = val.lower() if val else None
623 self._email = val.lower() if val else None
624
624
625 @hybrid_property
625 @hybrid_property
626 def first_name(self):
626 def first_name(self):
627 from rhodecode.lib import helpers as h
627 from rhodecode.lib import helpers as h
628 if self.name:
628 if self.name:
629 return h.escape(self.name)
629 return h.escape(self.name)
630 return self.name
630 return self.name
631
631
632 @hybrid_property
632 @hybrid_property
633 def last_name(self):
633 def last_name(self):
634 from rhodecode.lib import helpers as h
634 from rhodecode.lib import helpers as h
635 if self.lastname:
635 if self.lastname:
636 return h.escape(self.lastname)
636 return h.escape(self.lastname)
637 return self.lastname
637 return self.lastname
638
638
639 @hybrid_property
639 @hybrid_property
640 def api_key(self):
640 def api_key(self):
641 """
641 """
642 Fetch if exist an auth-token with role ALL connected to this user
642 Fetch if exist an auth-token with role ALL connected to this user
643 """
643 """
644 user_auth_token = UserApiKeys.query()\
644 user_auth_token = UserApiKeys.query()\
645 .filter(UserApiKeys.user_id == self.user_id)\
645 .filter(UserApiKeys.user_id == self.user_id)\
646 .filter(or_(UserApiKeys.expires == -1,
646 .filter(or_(UserApiKeys.expires == -1,
647 UserApiKeys.expires >= time.time()))\
647 UserApiKeys.expires >= time.time()))\
648 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
648 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
649 if user_auth_token:
649 if user_auth_token:
650 user_auth_token = user_auth_token.api_key
650 user_auth_token = user_auth_token.api_key
651
651
652 return user_auth_token
652 return user_auth_token
653
653
654 @api_key.setter
654 @api_key.setter
655 def api_key(self, val):
655 def api_key(self, val):
656 # don't allow to set API key this is deprecated for now
656 # don't allow to set API key this is deprecated for now
657 self._api_key = None
657 self._api_key = None
658
658
659 @property
659 @property
660 def reviewer_pull_requests(self):
660 def reviewer_pull_requests(self):
661 return PullRequestReviewers.query() \
661 return PullRequestReviewers.query() \
662 .options(joinedload(PullRequestReviewers.pull_request)) \
662 .options(joinedload(PullRequestReviewers.pull_request)) \
663 .filter(PullRequestReviewers.user_id == self.user_id) \
663 .filter(PullRequestReviewers.user_id == self.user_id) \
664 .all()
664 .all()
665
665
666 @property
666 @property
667 def firstname(self):
667 def firstname(self):
668 # alias for future
668 # alias for future
669 return self.name
669 return self.name
670
670
671 @property
671 @property
672 def emails(self):
672 def emails(self):
673 other = UserEmailMap.query()\
673 other = UserEmailMap.query()\
674 .filter(UserEmailMap.user == self) \
674 .filter(UserEmailMap.user == self) \
675 .order_by(UserEmailMap.email_id.asc()) \
675 .order_by(UserEmailMap.email_id.asc()) \
676 .all()
676 .all()
677 return [self.email] + [x.email for x in other]
677 return [self.email] + [x.email for x in other]
678
678
679 @property
679 @property
680 def auth_tokens(self):
680 def auth_tokens(self):
681 auth_tokens = self.get_auth_tokens()
681 auth_tokens = self.get_auth_tokens()
682 return [x.api_key for x in auth_tokens]
682 return [x.api_key for x in auth_tokens]
683
683
684 def get_auth_tokens(self):
684 def get_auth_tokens(self):
685 return UserApiKeys.query()\
685 return UserApiKeys.query()\
686 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.user == self)\
687 .order_by(UserApiKeys.user_api_key_id.asc())\
687 .order_by(UserApiKeys.user_api_key_id.asc())\
688 .all()
688 .all()
689
689
690 @LazyProperty
690 @LazyProperty
691 def feed_token(self):
691 def feed_token(self):
692 return self.get_feed_token()
692 return self.get_feed_token()
693
693
694 def get_feed_token(self, cache=True):
694 def get_feed_token(self, cache=True):
695 feed_tokens = UserApiKeys.query()\
695 feed_tokens = UserApiKeys.query()\
696 .filter(UserApiKeys.user == self)\
696 .filter(UserApiKeys.user == self)\
697 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
697 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
698 if cache:
698 if cache:
699 feed_tokens = feed_tokens.options(
699 feed_tokens = feed_tokens.options(
700 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
700 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
701
701
702 feed_tokens = feed_tokens.all()
702 feed_tokens = feed_tokens.all()
703 if feed_tokens:
703 if feed_tokens:
704 return feed_tokens[0].api_key
704 return feed_tokens[0].api_key
705 return 'NO_FEED_TOKEN_AVAILABLE'
705 return 'NO_FEED_TOKEN_AVAILABLE'
706
706
707 @classmethod
707 @classmethod
708 def get(cls, user_id, cache=False):
708 def get(cls, user_id, cache=False):
709 if not user_id:
709 if not user_id:
710 return
710 return
711
711
712 user = cls.query()
712 user = cls.query()
713 if cache:
713 if cache:
714 user = user.options(
714 user = user.options(
715 FromCache("sql_cache_short", "get_users_%s" % user_id))
715 FromCache("sql_cache_short", "get_users_%s" % user_id))
716 return user.get(user_id)
716 return user.get(user_id)
717
717
718 @classmethod
718 @classmethod
719 def extra_valid_auth_tokens(cls, user, role=None):
719 def extra_valid_auth_tokens(cls, user, role=None):
720 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
720 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
721 .filter(or_(UserApiKeys.expires == -1,
721 .filter(or_(UserApiKeys.expires == -1,
722 UserApiKeys.expires >= time.time()))
722 UserApiKeys.expires >= time.time()))
723 if role:
723 if role:
724 tokens = tokens.filter(or_(UserApiKeys.role == role,
724 tokens = tokens.filter(or_(UserApiKeys.role == role,
725 UserApiKeys.role == UserApiKeys.ROLE_ALL))
725 UserApiKeys.role == UserApiKeys.ROLE_ALL))
726 return tokens.all()
726 return tokens.all()
727
727
728 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
728 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
729 from rhodecode.lib import auth
729 from rhodecode.lib import auth
730
730
731 log.debug('Trying to authenticate user: %s via auth-token, '
731 log.debug('Trying to authenticate user: %s via auth-token, '
732 'and roles: %s', self, roles)
732 'and roles: %s', self, roles)
733
733
734 if not auth_token:
734 if not auth_token:
735 return False
735 return False
736
736
737 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
737 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
738 tokens_q = UserApiKeys.query()\
738 tokens_q = UserApiKeys.query()\
739 .filter(UserApiKeys.user_id == self.user_id)\
739 .filter(UserApiKeys.user_id == self.user_id)\
740 .filter(or_(UserApiKeys.expires == -1,
740 .filter(or_(UserApiKeys.expires == -1,
741 UserApiKeys.expires >= time.time()))
741 UserApiKeys.expires >= time.time()))
742
742
743 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
743 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
744
744
745 crypto_backend = auth.crypto_backend()
745 crypto_backend = auth.crypto_backend()
746 enc_token_map = {}
746 enc_token_map = {}
747 plain_token_map = {}
747 plain_token_map = {}
748 for token in tokens_q:
748 for token in tokens_q:
749 if token.api_key.startswith(crypto_backend.ENC_PREF):
749 if token.api_key.startswith(crypto_backend.ENC_PREF):
750 enc_token_map[token.api_key] = token
750 enc_token_map[token.api_key] = token
751 else:
751 else:
752 plain_token_map[token.api_key] = token
752 plain_token_map[token.api_key] = token
753 log.debug(
753 log.debug(
754 'Found %s plain and %s encrypted user tokens to check for authentication',
754 'Found %s plain and %s encrypted user tokens to check for authentication',
755 len(plain_token_map), len(enc_token_map))
755 len(plain_token_map), len(enc_token_map))
756
756
757 # plain token match comes first
757 # plain token match comes first
758 match = plain_token_map.get(auth_token)
758 match = plain_token_map.get(auth_token)
759
759
760 # check encrypted tokens now
760 # check encrypted tokens now
761 if not match:
761 if not match:
762 for token_hash, token in enc_token_map.items():
762 for token_hash, token in enc_token_map.items():
763 # NOTE(marcink): this is expensive to calculate, but most secure
763 # NOTE(marcink): this is expensive to calculate, but most secure
764 if crypto_backend.hash_check(auth_token, token_hash):
764 if crypto_backend.hash_check(auth_token, token_hash):
765 match = token
765 match = token
766 break
766 break
767
767
768 if match:
768 if match:
769 log.debug('Found matching token %s', match)
769 log.debug('Found matching token %s', match)
770 if match.repo_id:
770 if match.repo_id:
771 log.debug('Found scope, checking for scope match of token %s', match)
771 log.debug('Found scope, checking for scope match of token %s', match)
772 if match.repo_id == scope_repo_id:
772 if match.repo_id == scope_repo_id:
773 return True
773 return True
774 else:
774 else:
775 log.debug(
775 log.debug(
776 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
776 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
777 'and calling scope is:%s, skipping further checks',
777 'and calling scope is:%s, skipping further checks',
778 match.repo, scope_repo_id)
778 match.repo, scope_repo_id)
779 return False
779 return False
780 else:
780 else:
781 return True
781 return True
782
782
783 return False
783 return False
784
784
785 @property
785 @property
786 def ip_addresses(self):
786 def ip_addresses(self):
787 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
787 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
788 return [x.ip_addr for x in ret]
788 return [x.ip_addr for x in ret]
789
789
790 @property
790 @property
791 def username_and_name(self):
791 def username_and_name(self):
792 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
792 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
793
793
794 @property
794 @property
795 def username_or_name_or_email(self):
795 def username_or_name_or_email(self):
796 full_name = self.full_name if self.full_name is not ' ' else None
796 full_name = self.full_name if self.full_name is not ' ' else None
797 return self.username or full_name or self.email
797 return self.username or full_name or self.email
798
798
799 @property
799 @property
800 def full_name(self):
800 def full_name(self):
801 return '%s %s' % (self.first_name, self.last_name)
801 return '%s %s' % (self.first_name, self.last_name)
802
802
803 @property
803 @property
804 def full_name_or_username(self):
804 def full_name_or_username(self):
805 return ('%s %s' % (self.first_name, self.last_name)
805 return ('%s %s' % (self.first_name, self.last_name)
806 if (self.first_name and self.last_name) else self.username)
806 if (self.first_name and self.last_name) else self.username)
807
807
808 @property
808 @property
809 def full_contact(self):
809 def full_contact(self):
810 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
810 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
811
811
812 @property
812 @property
813 def short_contact(self):
813 def short_contact(self):
814 return '%s %s' % (self.first_name, self.last_name)
814 return '%s %s' % (self.first_name, self.last_name)
815
815
816 @property
816 @property
817 def is_admin(self):
817 def is_admin(self):
818 return self.admin
818 return self.admin
819
819
820 def AuthUser(self, **kwargs):
820 def AuthUser(self, **kwargs):
821 """
821 """
822 Returns instance of AuthUser for this user
822 Returns instance of AuthUser for this user
823 """
823 """
824 from rhodecode.lib.auth import AuthUser
824 from rhodecode.lib.auth import AuthUser
825 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
825 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
826
826
827 @hybrid_property
827 @hybrid_property
828 def user_data(self):
828 def user_data(self):
829 if not self._user_data:
829 if not self._user_data:
830 return {}
830 return {}
831
831
832 try:
832 try:
833 return json.loads(self._user_data)
833 return json.loads(self._user_data)
834 except TypeError:
834 except TypeError:
835 return {}
835 return {}
836
836
837 @user_data.setter
837 @user_data.setter
838 def user_data(self, val):
838 def user_data(self, val):
839 if not isinstance(val, dict):
839 if not isinstance(val, dict):
840 raise Exception('user_data must be dict, got %s' % type(val))
840 raise Exception('user_data must be dict, got %s' % type(val))
841 try:
841 try:
842 self._user_data = json.dumps(val)
842 self._user_data = json.dumps(val)
843 except Exception:
843 except Exception:
844 log.error(traceback.format_exc())
844 log.error(traceback.format_exc())
845
845
846 @classmethod
846 @classmethod
847 def get_by_username(cls, username, case_insensitive=False,
847 def get_by_username(cls, username, case_insensitive=False,
848 cache=False, identity_cache=False):
848 cache=False, identity_cache=False):
849 session = Session()
849 session = Session()
850
850
851 if case_insensitive:
851 if case_insensitive:
852 q = cls.query().filter(
852 q = cls.query().filter(
853 func.lower(cls.username) == func.lower(username))
853 func.lower(cls.username) == func.lower(username))
854 else:
854 else:
855 q = cls.query().filter(cls.username == username)
855 q = cls.query().filter(cls.username == username)
856
856
857 if cache:
857 if cache:
858 if identity_cache:
858 if identity_cache:
859 val = cls.identity_cache(session, 'username', username)
859 val = cls.identity_cache(session, 'username', username)
860 if val:
860 if val:
861 return val
861 return val
862 else:
862 else:
863 cache_key = "get_user_by_name_%s" % _hash_key(username)
863 cache_key = "get_user_by_name_%s" % _hash_key(username)
864 q = q.options(
864 q = q.options(
865 FromCache("sql_cache_short", cache_key))
865 FromCache("sql_cache_short", cache_key))
866
866
867 return q.scalar()
867 return q.scalar()
868
868
869 @classmethod
869 @classmethod
870 def get_by_auth_token(cls, auth_token, cache=False):
870 def get_by_auth_token(cls, auth_token, cache=False):
871 q = UserApiKeys.query()\
871 q = UserApiKeys.query()\
872 .filter(UserApiKeys.api_key == auth_token)\
872 .filter(UserApiKeys.api_key == auth_token)\
873 .filter(or_(UserApiKeys.expires == -1,
873 .filter(or_(UserApiKeys.expires == -1,
874 UserApiKeys.expires >= time.time()))
874 UserApiKeys.expires >= time.time()))
875 if cache:
875 if cache:
876 q = q.options(
876 q = q.options(
877 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
877 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
878
878
879 match = q.first()
879 match = q.first()
880 if match:
880 if match:
881 return match.user
881 return match.user
882
882
883 @classmethod
883 @classmethod
884 def get_by_email(cls, email, case_insensitive=False, cache=False):
884 def get_by_email(cls, email, case_insensitive=False, cache=False):
885
885
886 if case_insensitive:
886 if case_insensitive:
887 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
887 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
888
888
889 else:
889 else:
890 q = cls.query().filter(cls.email == email)
890 q = cls.query().filter(cls.email == email)
891
891
892 email_key = _hash_key(email)
892 email_key = _hash_key(email)
893 if cache:
893 if cache:
894 q = q.options(
894 q = q.options(
895 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
895 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
896
896
897 ret = q.scalar()
897 ret = q.scalar()
898 if ret is None:
898 if ret is None:
899 q = UserEmailMap.query()
899 q = UserEmailMap.query()
900 # try fetching in alternate email map
900 # try fetching in alternate email map
901 if case_insensitive:
901 if case_insensitive:
902 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
902 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
903 else:
903 else:
904 q = q.filter(UserEmailMap.email == email)
904 q = q.filter(UserEmailMap.email == email)
905 q = q.options(joinedload(UserEmailMap.user))
905 q = q.options(joinedload(UserEmailMap.user))
906 if cache:
906 if cache:
907 q = q.options(
907 q = q.options(
908 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
908 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
909 ret = getattr(q.scalar(), 'user', None)
909 ret = getattr(q.scalar(), 'user', None)
910
910
911 return ret
911 return ret
912
912
913 @classmethod
913 @classmethod
914 def get_from_cs_author(cls, author):
914 def get_from_cs_author(cls, author):
915 """
915 """
916 Tries to get User objects out of commit author string
916 Tries to get User objects out of commit author string
917
917
918 :param author:
918 :param author:
919 """
919 """
920 from rhodecode.lib.helpers import email, author_name
920 from rhodecode.lib.helpers import email, author_name
921 # Valid email in the attribute passed, see if they're in the system
921 # Valid email in the attribute passed, see if they're in the system
922 _email = email(author)
922 _email = email(author)
923 if _email:
923 if _email:
924 user = cls.get_by_email(_email, case_insensitive=True)
924 user = cls.get_by_email(_email, case_insensitive=True)
925 if user:
925 if user:
926 return user
926 return user
927 # Maybe we can match by username?
927 # Maybe we can match by username?
928 _author = author_name(author)
928 _author = author_name(author)
929 user = cls.get_by_username(_author, case_insensitive=True)
929 user = cls.get_by_username(_author, case_insensitive=True)
930 if user:
930 if user:
931 return user
931 return user
932
932
933 def update_userdata(self, **kwargs):
933 def update_userdata(self, **kwargs):
934 usr = self
934 usr = self
935 old = usr.user_data
935 old = usr.user_data
936 old.update(**kwargs)
936 old.update(**kwargs)
937 usr.user_data = old
937 usr.user_data = old
938 Session().add(usr)
938 Session().add(usr)
939 log.debug('updated userdata with ', kwargs)
939 log.debug('updated userdata with ', kwargs)
940
940
941 def update_lastlogin(self):
941 def update_lastlogin(self):
942 """Update user lastlogin"""
942 """Update user lastlogin"""
943 self.last_login = datetime.datetime.now()
943 self.last_login = datetime.datetime.now()
944 Session().add(self)
944 Session().add(self)
945 log.debug('updated user %s lastlogin', self.username)
945 log.debug('updated user %s lastlogin', self.username)
946
946
947 def update_password(self, new_password):
947 def update_password(self, new_password):
948 from rhodecode.lib.auth import get_crypt_password
948 from rhodecode.lib.auth import get_crypt_password
949
949
950 self.password = get_crypt_password(new_password)
950 self.password = get_crypt_password(new_password)
951 Session().add(self)
951 Session().add(self)
952
952
953 @classmethod
953 @classmethod
954 def get_first_super_admin(cls):
954 def get_first_super_admin(cls):
955 user = User.query()\
955 user = User.query()\
956 .filter(User.admin == true()) \
956 .filter(User.admin == true()) \
957 .order_by(User.user_id.asc()) \
957 .order_by(User.user_id.asc()) \
958 .first()
958 .first()
959
959
960 if user is None:
960 if user is None:
961 raise Exception('FATAL: Missing administrative account!')
961 raise Exception('FATAL: Missing administrative account!')
962 return user
962 return user
963
963
964 @classmethod
964 @classmethod
965 def get_all_super_admins(cls, only_active=False):
965 def get_all_super_admins(cls, only_active=False):
966 """
966 """
967 Returns all admin accounts sorted by username
967 Returns all admin accounts sorted by username
968 """
968 """
969 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
969 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
970 if only_active:
970 if only_active:
971 qry = qry.filter(User.active == true())
971 qry = qry.filter(User.active == true())
972 return qry.all()
972 return qry.all()
973
973
974 @classmethod
974 @classmethod
975 def get_default_user(cls, cache=False, refresh=False):
975 def get_default_user(cls, cache=False, refresh=False):
976 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
976 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
977 if user is None:
977 if user is None:
978 raise Exception('FATAL: Missing default account!')
978 raise Exception('FATAL: Missing default account!')
979 if refresh:
979 if refresh:
980 # The default user might be based on outdated state which
980 # The default user might be based on outdated state which
981 # has been loaded from the cache.
981 # has been loaded from the cache.
982 # A call to refresh() ensures that the
982 # A call to refresh() ensures that the
983 # latest state from the database is used.
983 # latest state from the database is used.
984 Session().refresh(user)
984 Session().refresh(user)
985 return user
985 return user
986
986
987 def _get_default_perms(self, user, suffix=''):
987 def _get_default_perms(self, user, suffix=''):
988 from rhodecode.model.permission import PermissionModel
988 from rhodecode.model.permission import PermissionModel
989 return PermissionModel().get_default_perms(user.user_perms, suffix)
989 return PermissionModel().get_default_perms(user.user_perms, suffix)
990
990
991 def get_default_perms(self, suffix=''):
991 def get_default_perms(self, suffix=''):
992 return self._get_default_perms(self, suffix)
992 return self._get_default_perms(self, suffix)
993
993
994 def get_api_data(self, include_secrets=False, details='full'):
994 def get_api_data(self, include_secrets=False, details='full'):
995 """
995 """
996 Common function for generating user related data for API
996 Common function for generating user related data for API
997
997
998 :param include_secrets: By default secrets in the API data will be replaced
998 :param include_secrets: By default secrets in the API data will be replaced
999 by a placeholder value to prevent exposing this data by accident. In case
999 by a placeholder value to prevent exposing this data by accident. In case
1000 this data shall be exposed, set this flag to ``True``.
1000 this data shall be exposed, set this flag to ``True``.
1001
1001
1002 :param details: details can be 'basic|full' basic gives only a subset of
1002 :param details: details can be 'basic|full' basic gives only a subset of
1003 the available user information that includes user_id, name and emails.
1003 the available user information that includes user_id, name and emails.
1004 """
1004 """
1005 user = self
1005 user = self
1006 user_data = self.user_data
1006 user_data = self.user_data
1007 data = {
1007 data = {
1008 'user_id': user.user_id,
1008 'user_id': user.user_id,
1009 'username': user.username,
1009 'username': user.username,
1010 'firstname': user.name,
1010 'firstname': user.name,
1011 'lastname': user.lastname,
1011 'lastname': user.lastname,
1012 'email': user.email,
1012 'email': user.email,
1013 'emails': user.emails,
1013 'emails': user.emails,
1014 }
1014 }
1015 if details == 'basic':
1015 if details == 'basic':
1016 return data
1016 return data
1017
1017
1018 auth_token_length = 40
1018 auth_token_length = 40
1019 auth_token_replacement = '*' * auth_token_length
1019 auth_token_replacement = '*' * auth_token_length
1020
1020
1021 extras = {
1021 extras = {
1022 'auth_tokens': [auth_token_replacement],
1022 'auth_tokens': [auth_token_replacement],
1023 'active': user.active,
1023 'active': user.active,
1024 'admin': user.admin,
1024 'admin': user.admin,
1025 'extern_type': user.extern_type,
1025 'extern_type': user.extern_type,
1026 'extern_name': user.extern_name,
1026 'extern_name': user.extern_name,
1027 'last_login': user.last_login,
1027 'last_login': user.last_login,
1028 'last_activity': user.last_activity,
1028 'last_activity': user.last_activity,
1029 'ip_addresses': user.ip_addresses,
1029 'ip_addresses': user.ip_addresses,
1030 'language': user_data.get('language')
1030 'language': user_data.get('language')
1031 }
1031 }
1032 data.update(extras)
1032 data.update(extras)
1033
1033
1034 if include_secrets:
1034 if include_secrets:
1035 data['auth_tokens'] = user.auth_tokens
1035 data['auth_tokens'] = user.auth_tokens
1036 return data
1036 return data
1037
1037
1038 def __json__(self):
1038 def __json__(self):
1039 data = {
1039 data = {
1040 'full_name': self.full_name,
1040 'full_name': self.full_name,
1041 'full_name_or_username': self.full_name_or_username,
1041 'full_name_or_username': self.full_name_or_username,
1042 'short_contact': self.short_contact,
1042 'short_contact': self.short_contact,
1043 'full_contact': self.full_contact,
1043 'full_contact': self.full_contact,
1044 }
1044 }
1045 data.update(self.get_api_data())
1045 data.update(self.get_api_data())
1046 return data
1046 return data
1047
1047
1048
1048
1049 class UserApiKeys(Base, BaseModel):
1049 class UserApiKeys(Base, BaseModel):
1050 __tablename__ = 'user_api_keys'
1050 __tablename__ = 'user_api_keys'
1051 __table_args__ = (
1051 __table_args__ = (
1052 Index('uak_api_key_idx', 'api_key', unique=True),
1052 Index('uak_api_key_idx', 'api_key', unique=True),
1053 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1053 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1054 base_table_args
1054 base_table_args
1055 )
1055 )
1056 __mapper_args__ = {}
1056 __mapper_args__ = {}
1057
1057
1058 # ApiKey role
1058 # ApiKey role
1059 ROLE_ALL = 'token_role_all'
1059 ROLE_ALL = 'token_role_all'
1060 ROLE_HTTP = 'token_role_http'
1060 ROLE_HTTP = 'token_role_http'
1061 ROLE_VCS = 'token_role_vcs'
1061 ROLE_VCS = 'token_role_vcs'
1062 ROLE_API = 'token_role_api'
1062 ROLE_API = 'token_role_api'
1063 ROLE_FEED = 'token_role_feed'
1063 ROLE_FEED = 'token_role_feed'
1064 ROLE_PASSWORD_RESET = 'token_password_reset'
1064 ROLE_PASSWORD_RESET = 'token_password_reset'
1065
1065
1066 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1066 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1067
1067
1068 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1068 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1069 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1069 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1070 api_key = Column("api_key", String(255), nullable=False, unique=True)
1070 api_key = Column("api_key", String(255), nullable=False, unique=True)
1071 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1071 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1072 expires = Column('expires', Float(53), nullable=False)
1072 expires = Column('expires', Float(53), nullable=False)
1073 role = Column('role', String(255), nullable=True)
1073 role = Column('role', String(255), nullable=True)
1074 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1074 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1075
1075
1076 # scope columns
1076 # scope columns
1077 repo_id = Column(
1077 repo_id = Column(
1078 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1078 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1079 nullable=True, unique=None, default=None)
1079 nullable=True, unique=None, default=None)
1080 repo = relationship('Repository', lazy='joined')
1080 repo = relationship('Repository', lazy='joined')
1081
1081
1082 repo_group_id = Column(
1082 repo_group_id = Column(
1083 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1083 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1084 nullable=True, unique=None, default=None)
1084 nullable=True, unique=None, default=None)
1085 repo_group = relationship('RepoGroup', lazy='joined')
1085 repo_group = relationship('RepoGroup', lazy='joined')
1086
1086
1087 user = relationship('User', lazy='joined')
1087 user = relationship('User', lazy='joined')
1088
1088
1089 def __unicode__(self):
1089 def __unicode__(self):
1090 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1090 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1091
1091
1092 def __json__(self):
1092 def __json__(self):
1093 data = {
1093 data = {
1094 'auth_token': self.api_key,
1094 'auth_token': self.api_key,
1095 'role': self.role,
1095 'role': self.role,
1096 'scope': self.scope_humanized,
1096 'scope': self.scope_humanized,
1097 'expired': self.expired
1097 'expired': self.expired
1098 }
1098 }
1099 return data
1099 return data
1100
1100
1101 def get_api_data(self, include_secrets=False):
1101 def get_api_data(self, include_secrets=False):
1102 data = self.__json__()
1102 data = self.__json__()
1103 if include_secrets:
1103 if include_secrets:
1104 return data
1104 return data
1105 else:
1105 else:
1106 data['auth_token'] = self.token_obfuscated
1106 data['auth_token'] = self.token_obfuscated
1107 return data
1107 return data
1108
1108
1109 @hybrid_property
1109 @hybrid_property
1110 def description_safe(self):
1110 def description_safe(self):
1111 from rhodecode.lib import helpers as h
1111 from rhodecode.lib import helpers as h
1112 return h.escape(self.description)
1112 return h.escape(self.description)
1113
1113
1114 @property
1114 @property
1115 def expired(self):
1115 def expired(self):
1116 if self.expires == -1:
1116 if self.expires == -1:
1117 return False
1117 return False
1118 return time.time() > self.expires
1118 return time.time() > self.expires
1119
1119
1120 @classmethod
1120 @classmethod
1121 def _get_role_name(cls, role):
1121 def _get_role_name(cls, role):
1122 return {
1122 return {
1123 cls.ROLE_ALL: _('all'),
1123 cls.ROLE_ALL: _('all'),
1124 cls.ROLE_HTTP: _('http/web interface'),
1124 cls.ROLE_HTTP: _('http/web interface'),
1125 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1125 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1126 cls.ROLE_API: _('api calls'),
1126 cls.ROLE_API: _('api calls'),
1127 cls.ROLE_FEED: _('feed access'),
1127 cls.ROLE_FEED: _('feed access'),
1128 }.get(role, role)
1128 }.get(role, role)
1129
1129
1130 @property
1130 @property
1131 def role_humanized(self):
1131 def role_humanized(self):
1132 return self._get_role_name(self.role)
1132 return self._get_role_name(self.role)
1133
1133
1134 def _get_scope(self):
1134 def _get_scope(self):
1135 if self.repo:
1135 if self.repo:
1136 return 'Repository: {}'.format(self.repo.repo_name)
1136 return 'Repository: {}'.format(self.repo.repo_name)
1137 if self.repo_group:
1137 if self.repo_group:
1138 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1138 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1139 return 'Global'
1139 return 'Global'
1140
1140
1141 @property
1141 @property
1142 def scope_humanized(self):
1142 def scope_humanized(self):
1143 return self._get_scope()
1143 return self._get_scope()
1144
1144
1145 @property
1145 @property
1146 def token_obfuscated(self):
1146 def token_obfuscated(self):
1147 if self.api_key:
1147 if self.api_key:
1148 return self.api_key[:4] + "****"
1148 return self.api_key[:4] + "****"
1149
1149
1150
1150
1151 class UserEmailMap(Base, BaseModel):
1151 class UserEmailMap(Base, BaseModel):
1152 __tablename__ = 'user_email_map'
1152 __tablename__ = 'user_email_map'
1153 __table_args__ = (
1153 __table_args__ = (
1154 Index('uem_email_idx', 'email'),
1154 Index('uem_email_idx', 'email'),
1155 UniqueConstraint('email'),
1155 UniqueConstraint('email'),
1156 base_table_args
1156 base_table_args
1157 )
1157 )
1158 __mapper_args__ = {}
1158 __mapper_args__ = {}
1159
1159
1160 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1160 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1161 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1161 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1162 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1162 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1163 user = relationship('User', lazy='joined')
1163 user = relationship('User', lazy='joined')
1164
1164
1165 @validates('_email')
1165 @validates('_email')
1166 def validate_email(self, key, email):
1166 def validate_email(self, key, email):
1167 # check if this email is not main one
1167 # check if this email is not main one
1168 main_email = Session().query(User).filter(User.email == email).scalar()
1168 main_email = Session().query(User).filter(User.email == email).scalar()
1169 if main_email is not None:
1169 if main_email is not None:
1170 raise AttributeError('email %s is present is user table' % email)
1170 raise AttributeError('email %s is present is user table' % email)
1171 return email
1171 return email
1172
1172
1173 @hybrid_property
1173 @hybrid_property
1174 def email(self):
1174 def email(self):
1175 return self._email
1175 return self._email
1176
1176
1177 @email.setter
1177 @email.setter
1178 def email(self, val):
1178 def email(self, val):
1179 self._email = val.lower() if val else None
1179 self._email = val.lower() if val else None
1180
1180
1181
1181
1182 class UserIpMap(Base, BaseModel):
1182 class UserIpMap(Base, BaseModel):
1183 __tablename__ = 'user_ip_map'
1183 __tablename__ = 'user_ip_map'
1184 __table_args__ = (
1184 __table_args__ = (
1185 UniqueConstraint('user_id', 'ip_addr'),
1185 UniqueConstraint('user_id', 'ip_addr'),
1186 base_table_args
1186 base_table_args
1187 )
1187 )
1188 __mapper_args__ = {}
1188 __mapper_args__ = {}
1189
1189
1190 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1190 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1192 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1192 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1193 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1193 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1194 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1194 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1195 user = relationship('User', lazy='joined')
1195 user = relationship('User', lazy='joined')
1196
1196
1197 @hybrid_property
1197 @hybrid_property
1198 def description_safe(self):
1198 def description_safe(self):
1199 from rhodecode.lib import helpers as h
1199 from rhodecode.lib import helpers as h
1200 return h.escape(self.description)
1200 return h.escape(self.description)
1201
1201
1202 @classmethod
1202 @classmethod
1203 def _get_ip_range(cls, ip_addr):
1203 def _get_ip_range(cls, ip_addr):
1204 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1204 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1205 return [str(net.network_address), str(net.broadcast_address)]
1205 return [str(net.network_address), str(net.broadcast_address)]
1206
1206
1207 def __json__(self):
1207 def __json__(self):
1208 return {
1208 return {
1209 'ip_addr': self.ip_addr,
1209 'ip_addr': self.ip_addr,
1210 'ip_range': self._get_ip_range(self.ip_addr),
1210 'ip_range': self._get_ip_range(self.ip_addr),
1211 }
1211 }
1212
1212
1213 def __unicode__(self):
1213 def __unicode__(self):
1214 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1214 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1215 self.user_id, self.ip_addr)
1215 self.user_id, self.ip_addr)
1216
1216
1217
1217
1218 class UserSshKeys(Base, BaseModel):
1218 class UserSshKeys(Base, BaseModel):
1219 __tablename__ = 'user_ssh_keys'
1219 __tablename__ = 'user_ssh_keys'
1220 __table_args__ = (
1220 __table_args__ = (
1221 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1221 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1222
1222
1223 UniqueConstraint('ssh_key_fingerprint'),
1223 UniqueConstraint('ssh_key_fingerprint'),
1224
1224
1225 base_table_args
1225 base_table_args
1226 )
1226 )
1227 __mapper_args__ = {}
1227 __mapper_args__ = {}
1228
1228
1229 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1229 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1230 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1230 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1231 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1231 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1232
1232
1233 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1233 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1234
1234
1235 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1235 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1236 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1236 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1237 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1237 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1238
1238
1239 user = relationship('User', lazy='joined')
1239 user = relationship('User', lazy='joined')
1240
1240
1241 def __json__(self):
1241 def __json__(self):
1242 data = {
1242 data = {
1243 'ssh_fingerprint': self.ssh_key_fingerprint,
1243 'ssh_fingerprint': self.ssh_key_fingerprint,
1244 'description': self.description,
1244 'description': self.description,
1245 'created_on': self.created_on
1245 'created_on': self.created_on
1246 }
1246 }
1247 return data
1247 return data
1248
1248
1249 def get_api_data(self):
1249 def get_api_data(self):
1250 data = self.__json__()
1250 data = self.__json__()
1251 return data
1251 return data
1252
1252
1253
1253
1254 class UserLog(Base, BaseModel):
1254 class UserLog(Base, BaseModel):
1255 __tablename__ = 'user_logs'
1255 __tablename__ = 'user_logs'
1256 __table_args__ = (
1256 __table_args__ = (
1257 base_table_args,
1257 base_table_args,
1258 )
1258 )
1259
1259
1260 VERSION_1 = 'v1'
1260 VERSION_1 = 'v1'
1261 VERSION_2 = 'v2'
1261 VERSION_2 = 'v2'
1262 VERSIONS = [VERSION_1, VERSION_2]
1262 VERSIONS = [VERSION_1, VERSION_2]
1263
1263
1264 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1264 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1265 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1265 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1266 username = Column("username", String(255), nullable=True, unique=None, default=None)
1266 username = Column("username", String(255), nullable=True, unique=None, default=None)
1267 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1267 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1268 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1268 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1269 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1269 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1270 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1270 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1271 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1271 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1272
1272
1273 version = Column("version", String(255), nullable=True, default=VERSION_1)
1273 version = Column("version", String(255), nullable=True, default=VERSION_1)
1274 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1274 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1275 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1275 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1276
1276
1277 def __unicode__(self):
1277 def __unicode__(self):
1278 return u"<%s('id:%s:%s')>" % (
1278 return u"<%s('id:%s:%s')>" % (
1279 self.__class__.__name__, self.repository_name, self.action)
1279 self.__class__.__name__, self.repository_name, self.action)
1280
1280
1281 def __json__(self):
1281 def __json__(self):
1282 return {
1282 return {
1283 'user_id': self.user_id,
1283 'user_id': self.user_id,
1284 'username': self.username,
1284 'username': self.username,
1285 'repository_id': self.repository_id,
1285 'repository_id': self.repository_id,
1286 'repository_name': self.repository_name,
1286 'repository_name': self.repository_name,
1287 'user_ip': self.user_ip,
1287 'user_ip': self.user_ip,
1288 'action_date': self.action_date,
1288 'action_date': self.action_date,
1289 'action': self.action,
1289 'action': self.action,
1290 }
1290 }
1291
1291
1292 @hybrid_property
1292 @hybrid_property
1293 def entry_id(self):
1293 def entry_id(self):
1294 return self.user_log_id
1294 return self.user_log_id
1295
1295
1296 @property
1296 @property
1297 def action_as_day(self):
1297 def action_as_day(self):
1298 return datetime.date(*self.action_date.timetuple()[:3])
1298 return datetime.date(*self.action_date.timetuple()[:3])
1299
1299
1300 user = relationship('User')
1300 user = relationship('User')
1301 repository = relationship('Repository', cascade='')
1301 repository = relationship('Repository', cascade='')
1302
1302
1303
1303
1304 class UserGroup(Base, BaseModel):
1304 class UserGroup(Base, BaseModel):
1305 __tablename__ = 'users_groups'
1305 __tablename__ = 'users_groups'
1306 __table_args__ = (
1306 __table_args__ = (
1307 base_table_args,
1307 base_table_args,
1308 )
1308 )
1309
1309
1310 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1310 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1311 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1311 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1312 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1312 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1313 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1313 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1314 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1314 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1315 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1315 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1317 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1317 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1318
1318
1319 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1319 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1320 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1320 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1321 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1321 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1322 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1322 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1323 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1323 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1324 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1324 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1325
1325
1326 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1326 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1327 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1327 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1328
1328
1329 @classmethod
1329 @classmethod
1330 def _load_group_data(cls, column):
1330 def _load_group_data(cls, column):
1331 if not column:
1331 if not column:
1332 return {}
1332 return {}
1333
1333
1334 try:
1334 try:
1335 return json.loads(column) or {}
1335 return json.loads(column) or {}
1336 except TypeError:
1336 except TypeError:
1337 return {}
1337 return {}
1338
1338
1339 @hybrid_property
1339 @hybrid_property
1340 def description_safe(self):
1340 def description_safe(self):
1341 from rhodecode.lib import helpers as h
1341 from rhodecode.lib import helpers as h
1342 return h.escape(self.user_group_description)
1342 return h.escape(self.user_group_description)
1343
1343
1344 @hybrid_property
1344 @hybrid_property
1345 def group_data(self):
1345 def group_data(self):
1346 return self._load_group_data(self._group_data)
1346 return self._load_group_data(self._group_data)
1347
1347
1348 @group_data.expression
1348 @group_data.expression
1349 def group_data(self, **kwargs):
1349 def group_data(self, **kwargs):
1350 return self._group_data
1350 return self._group_data
1351
1351
1352 @group_data.setter
1352 @group_data.setter
1353 def group_data(self, val):
1353 def group_data(self, val):
1354 try:
1354 try:
1355 self._group_data = json.dumps(val)
1355 self._group_data = json.dumps(val)
1356 except Exception:
1356 except Exception:
1357 log.error(traceback.format_exc())
1357 log.error(traceback.format_exc())
1358
1358
1359 @classmethod
1359 @classmethod
1360 def _load_sync(cls, group_data):
1360 def _load_sync(cls, group_data):
1361 if group_data:
1361 if group_data:
1362 return group_data.get('extern_type')
1362 return group_data.get('extern_type')
1363
1363
1364 @property
1364 @property
1365 def sync(self):
1365 def sync(self):
1366 return self._load_sync(self.group_data)
1366 return self._load_sync(self.group_data)
1367
1367
1368 def __unicode__(self):
1368 def __unicode__(self):
1369 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1369 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1370 self.users_group_id,
1370 self.users_group_id,
1371 self.users_group_name)
1371 self.users_group_name)
1372
1372
1373 @classmethod
1373 @classmethod
1374 def get_by_group_name(cls, group_name, cache=False,
1374 def get_by_group_name(cls, group_name, cache=False,
1375 case_insensitive=False):
1375 case_insensitive=False):
1376 if case_insensitive:
1376 if case_insensitive:
1377 q = cls.query().filter(func.lower(cls.users_group_name) ==
1377 q = cls.query().filter(func.lower(cls.users_group_name) ==
1378 func.lower(group_name))
1378 func.lower(group_name))
1379
1379
1380 else:
1380 else:
1381 q = cls.query().filter(cls.users_group_name == group_name)
1381 q = cls.query().filter(cls.users_group_name == group_name)
1382 if cache:
1382 if cache:
1383 q = q.options(
1383 q = q.options(
1384 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1384 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1385 return q.scalar()
1385 return q.scalar()
1386
1386
1387 @classmethod
1387 @classmethod
1388 def get(cls, user_group_id, cache=False):
1388 def get(cls, user_group_id, cache=False):
1389 if not user_group_id:
1389 if not user_group_id:
1390 return
1390 return
1391
1391
1392 user_group = cls.query()
1392 user_group = cls.query()
1393 if cache:
1393 if cache:
1394 user_group = user_group.options(
1394 user_group = user_group.options(
1395 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1395 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1396 return user_group.get(user_group_id)
1396 return user_group.get(user_group_id)
1397
1397
1398 def permissions(self, with_admins=True, with_owner=True,
1398 def permissions(self, with_admins=True, with_owner=True,
1399 expand_from_user_groups=False):
1399 expand_from_user_groups=False):
1400 """
1400 """
1401 Permissions for user groups
1401 Permissions for user groups
1402 """
1402 """
1403 _admin_perm = 'usergroup.admin'
1403 _admin_perm = 'usergroup.admin'
1404
1404
1405 owner_row = []
1405 owner_row = []
1406 if with_owner:
1406 if with_owner:
1407 usr = AttributeDict(self.user.get_dict())
1407 usr = AttributeDict(self.user.get_dict())
1408 usr.owner_row = True
1408 usr.owner_row = True
1409 usr.permission = _admin_perm
1409 usr.permission = _admin_perm
1410 owner_row.append(usr)
1410 owner_row.append(usr)
1411
1411
1412 super_admin_ids = []
1412 super_admin_ids = []
1413 super_admin_rows = []
1413 super_admin_rows = []
1414 if with_admins:
1414 if with_admins:
1415 for usr in User.get_all_super_admins():
1415 for usr in User.get_all_super_admins():
1416 super_admin_ids.append(usr.user_id)
1416 super_admin_ids.append(usr.user_id)
1417 # if this admin is also owner, don't double the record
1417 # if this admin is also owner, don't double the record
1418 if usr.user_id == owner_row[0].user_id:
1418 if usr.user_id == owner_row[0].user_id:
1419 owner_row[0].admin_row = True
1419 owner_row[0].admin_row = True
1420 else:
1420 else:
1421 usr = AttributeDict(usr.get_dict())
1421 usr = AttributeDict(usr.get_dict())
1422 usr.admin_row = True
1422 usr.admin_row = True
1423 usr.permission = _admin_perm
1423 usr.permission = _admin_perm
1424 super_admin_rows.append(usr)
1424 super_admin_rows.append(usr)
1425
1425
1426 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1426 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1427 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1427 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1428 joinedload(UserUserGroupToPerm.user),
1428 joinedload(UserUserGroupToPerm.user),
1429 joinedload(UserUserGroupToPerm.permission),)
1429 joinedload(UserUserGroupToPerm.permission),)
1430
1430
1431 # get owners and admins and permissions. We do a trick of re-writing
1431 # get owners and admins and permissions. We do a trick of re-writing
1432 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1432 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1433 # has a global reference and changing one object propagates to all
1433 # has a global reference and changing one object propagates to all
1434 # others. This means if admin is also an owner admin_row that change
1434 # others. This means if admin is also an owner admin_row that change
1435 # would propagate to both objects
1435 # would propagate to both objects
1436 perm_rows = []
1436 perm_rows = []
1437 for _usr in q.all():
1437 for _usr in q.all():
1438 usr = AttributeDict(_usr.user.get_dict())
1438 usr = AttributeDict(_usr.user.get_dict())
1439 # if this user is also owner/admin, mark as duplicate record
1439 # if this user is also owner/admin, mark as duplicate record
1440 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1440 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1441 usr.duplicate_perm = True
1441 usr.duplicate_perm = True
1442 usr.permission = _usr.permission.permission_name
1442 usr.permission = _usr.permission.permission_name
1443 perm_rows.append(usr)
1443 perm_rows.append(usr)
1444
1444
1445 # filter the perm rows by 'default' first and then sort them by
1445 # filter the perm rows by 'default' first and then sort them by
1446 # admin,write,read,none permissions sorted again alphabetically in
1446 # admin,write,read,none permissions sorted again alphabetically in
1447 # each group
1447 # each group
1448 perm_rows = sorted(perm_rows, key=display_user_sort)
1448 perm_rows = sorted(perm_rows, key=display_user_sort)
1449
1449
1450 user_groups_rows = []
1450 user_groups_rows = []
1451 if expand_from_user_groups:
1451 if expand_from_user_groups:
1452 for ug in self.permission_user_groups(with_members=True):
1452 for ug in self.permission_user_groups(with_members=True):
1453 for user_data in ug.members:
1453 for user_data in ug.members:
1454 user_groups_rows.append(user_data)
1454 user_groups_rows.append(user_data)
1455
1455
1456 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1456 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1457
1457
1458 def permission_user_groups(self, with_members=False):
1458 def permission_user_groups(self, with_members=False):
1459 q = UserGroupUserGroupToPerm.query()\
1459 q = UserGroupUserGroupToPerm.query()\
1460 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1460 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1461 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1461 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1462 joinedload(UserGroupUserGroupToPerm.target_user_group),
1462 joinedload(UserGroupUserGroupToPerm.target_user_group),
1463 joinedload(UserGroupUserGroupToPerm.permission),)
1463 joinedload(UserGroupUserGroupToPerm.permission),)
1464
1464
1465 perm_rows = []
1465 perm_rows = []
1466 for _user_group in q.all():
1466 for _user_group in q.all():
1467 entry = AttributeDict(_user_group.user_group.get_dict())
1467 entry = AttributeDict(_user_group.user_group.get_dict())
1468 entry.permission = _user_group.permission.permission_name
1468 entry.permission = _user_group.permission.permission_name
1469 if with_members:
1469 if with_members:
1470 entry.members = [x.user.get_dict()
1470 entry.members = [x.user.get_dict()
1471 for x in _user_group.users_group.members]
1471 for x in _user_group.users_group.members]
1472 perm_rows.append(entry)
1472 perm_rows.append(entry)
1473
1473
1474 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1474 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1475 return perm_rows
1475 return perm_rows
1476
1476
1477 def _get_default_perms(self, user_group, suffix=''):
1477 def _get_default_perms(self, user_group, suffix=''):
1478 from rhodecode.model.permission import PermissionModel
1478 from rhodecode.model.permission import PermissionModel
1479 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1479 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1480
1480
1481 def get_default_perms(self, suffix=''):
1481 def get_default_perms(self, suffix=''):
1482 return self._get_default_perms(self, suffix)
1482 return self._get_default_perms(self, suffix)
1483
1483
1484 def get_api_data(self, with_group_members=True, include_secrets=False):
1484 def get_api_data(self, with_group_members=True, include_secrets=False):
1485 """
1485 """
1486 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1486 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1487 basically forwarded.
1487 basically forwarded.
1488
1488
1489 """
1489 """
1490 user_group = self
1490 user_group = self
1491 data = {
1491 data = {
1492 'users_group_id': user_group.users_group_id,
1492 'users_group_id': user_group.users_group_id,
1493 'group_name': user_group.users_group_name,
1493 'group_name': user_group.users_group_name,
1494 'group_description': user_group.user_group_description,
1494 'group_description': user_group.user_group_description,
1495 'active': user_group.users_group_active,
1495 'active': user_group.users_group_active,
1496 'owner': user_group.user.username,
1496 'owner': user_group.user.username,
1497 'sync': user_group.sync,
1497 'sync': user_group.sync,
1498 'owner_email': user_group.user.email,
1498 'owner_email': user_group.user.email,
1499 }
1499 }
1500
1500
1501 if with_group_members:
1501 if with_group_members:
1502 users = []
1502 users = []
1503 for user in user_group.members:
1503 for user in user_group.members:
1504 user = user.user
1504 user = user.user
1505 users.append(user.get_api_data(include_secrets=include_secrets))
1505 users.append(user.get_api_data(include_secrets=include_secrets))
1506 data['users'] = users
1506 data['users'] = users
1507
1507
1508 return data
1508 return data
1509
1509
1510
1510
1511 class UserGroupMember(Base, BaseModel):
1511 class UserGroupMember(Base, BaseModel):
1512 __tablename__ = 'users_groups_members'
1512 __tablename__ = 'users_groups_members'
1513 __table_args__ = (
1513 __table_args__ = (
1514 base_table_args,
1514 base_table_args,
1515 )
1515 )
1516
1516
1517 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1517 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1518 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1518 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1519 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1519 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1520
1520
1521 user = relationship('User', lazy='joined')
1521 user = relationship('User', lazy='joined')
1522 users_group = relationship('UserGroup')
1522 users_group = relationship('UserGroup')
1523
1523
1524 def __init__(self, gr_id='', u_id=''):
1524 def __init__(self, gr_id='', u_id=''):
1525 self.users_group_id = gr_id
1525 self.users_group_id = gr_id
1526 self.user_id = u_id
1526 self.user_id = u_id
1527
1527
1528
1528
1529 class RepositoryField(Base, BaseModel):
1529 class RepositoryField(Base, BaseModel):
1530 __tablename__ = 'repositories_fields'
1530 __tablename__ = 'repositories_fields'
1531 __table_args__ = (
1531 __table_args__ = (
1532 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1532 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1533 base_table_args,
1533 base_table_args,
1534 )
1534 )
1535
1535
1536 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1536 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1537
1537
1538 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1538 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1539 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1539 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1540 field_key = Column("field_key", String(250))
1540 field_key = Column("field_key", String(250))
1541 field_label = Column("field_label", String(1024), nullable=False)
1541 field_label = Column("field_label", String(1024), nullable=False)
1542 field_value = Column("field_value", String(10000), nullable=False)
1542 field_value = Column("field_value", String(10000), nullable=False)
1543 field_desc = Column("field_desc", String(1024), nullable=False)
1543 field_desc = Column("field_desc", String(1024), nullable=False)
1544 field_type = Column("field_type", String(255), nullable=False, unique=None)
1544 field_type = Column("field_type", String(255), nullable=False, unique=None)
1545 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1545 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1546
1546
1547 repository = relationship('Repository')
1547 repository = relationship('Repository')
1548
1548
1549 @property
1549 @property
1550 def field_key_prefixed(self):
1550 def field_key_prefixed(self):
1551 return 'ex_%s' % self.field_key
1551 return 'ex_%s' % self.field_key
1552
1552
1553 @classmethod
1553 @classmethod
1554 def un_prefix_key(cls, key):
1554 def un_prefix_key(cls, key):
1555 if key.startswith(cls.PREFIX):
1555 if key.startswith(cls.PREFIX):
1556 return key[len(cls.PREFIX):]
1556 return key[len(cls.PREFIX):]
1557 return key
1557 return key
1558
1558
1559 @classmethod
1559 @classmethod
1560 def get_by_key_name(cls, key, repo):
1560 def get_by_key_name(cls, key, repo):
1561 row = cls.query()\
1561 row = cls.query()\
1562 .filter(cls.repository == repo)\
1562 .filter(cls.repository == repo)\
1563 .filter(cls.field_key == key).scalar()
1563 .filter(cls.field_key == key).scalar()
1564 return row
1564 return row
1565
1565
1566
1566
1567 class Repository(Base, BaseModel):
1567 class Repository(Base, BaseModel):
1568 __tablename__ = 'repositories'
1568 __tablename__ = 'repositories'
1569 __table_args__ = (
1569 __table_args__ = (
1570 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1570 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1571 base_table_args,
1571 base_table_args,
1572 )
1572 )
1573 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1573 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1574 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1574 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1575 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1575 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1576
1576
1577 STATE_CREATED = 'repo_state_created'
1577 STATE_CREATED = 'repo_state_created'
1578 STATE_PENDING = 'repo_state_pending'
1578 STATE_PENDING = 'repo_state_pending'
1579 STATE_ERROR = 'repo_state_error'
1579 STATE_ERROR = 'repo_state_error'
1580
1580
1581 LOCK_AUTOMATIC = 'lock_auto'
1581 LOCK_AUTOMATIC = 'lock_auto'
1582 LOCK_API = 'lock_api'
1582 LOCK_API = 'lock_api'
1583 LOCK_WEB = 'lock_web'
1583 LOCK_WEB = 'lock_web'
1584 LOCK_PULL = 'lock_pull'
1584 LOCK_PULL = 'lock_pull'
1585
1585
1586 NAME_SEP = URL_SEP
1586 NAME_SEP = URL_SEP
1587
1587
1588 repo_id = Column(
1588 repo_id = Column(
1589 "repo_id", Integer(), nullable=False, unique=True, default=None,
1589 "repo_id", Integer(), nullable=False, unique=True, default=None,
1590 primary_key=True)
1590 primary_key=True)
1591 _repo_name = Column(
1591 _repo_name = Column(
1592 "repo_name", Text(), nullable=False, default=None)
1592 "repo_name", Text(), nullable=False, default=None)
1593 _repo_name_hash = Column(
1593 _repo_name_hash = Column(
1594 "repo_name_hash", String(255), nullable=False, unique=True)
1594 "repo_name_hash", String(255), nullable=False, unique=True)
1595 repo_state = Column("repo_state", String(255), nullable=True)
1595 repo_state = Column("repo_state", String(255), nullable=True)
1596
1596
1597 clone_uri = Column(
1597 clone_uri = Column(
1598 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1598 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1599 default=None)
1599 default=None)
1600 push_uri = Column(
1600 push_uri = Column(
1601 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1601 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1602 default=None)
1602 default=None)
1603 repo_type = Column(
1603 repo_type = Column(
1604 "repo_type", String(255), nullable=False, unique=False, default=None)
1604 "repo_type", String(255), nullable=False, unique=False, default=None)
1605 user_id = Column(
1605 user_id = Column(
1606 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1606 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1607 unique=False, default=None)
1607 unique=False, default=None)
1608 private = Column(
1608 private = Column(
1609 "private", Boolean(), nullable=True, unique=None, default=None)
1609 "private", Boolean(), nullable=True, unique=None, default=None)
1610 archived = Column(
1610 archived = Column(
1611 "archived", Boolean(), nullable=True, unique=None, default=None)
1611 "archived", Boolean(), nullable=True, unique=None, default=None)
1612 enable_statistics = Column(
1612 enable_statistics = Column(
1613 "statistics", Boolean(), nullable=True, unique=None, default=True)
1613 "statistics", Boolean(), nullable=True, unique=None, default=True)
1614 enable_downloads = Column(
1614 enable_downloads = Column(
1615 "downloads", Boolean(), nullable=True, unique=None, default=True)
1615 "downloads", Boolean(), nullable=True, unique=None, default=True)
1616 description = Column(
1616 description = Column(
1617 "description", String(10000), nullable=True, unique=None, default=None)
1617 "description", String(10000), nullable=True, unique=None, default=None)
1618 created_on = Column(
1618 created_on = Column(
1619 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1619 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1620 default=datetime.datetime.now)
1620 default=datetime.datetime.now)
1621 updated_on = Column(
1621 updated_on = Column(
1622 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1622 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1623 default=datetime.datetime.now)
1623 default=datetime.datetime.now)
1624 _landing_revision = Column(
1624 _landing_revision = Column(
1625 "landing_revision", String(255), nullable=False, unique=False,
1625 "landing_revision", String(255), nullable=False, unique=False,
1626 default=None)
1626 default=None)
1627 enable_locking = Column(
1627 enable_locking = Column(
1628 "enable_locking", Boolean(), nullable=False, unique=None,
1628 "enable_locking", Boolean(), nullable=False, unique=None,
1629 default=False)
1629 default=False)
1630 _locked = Column(
1630 _locked = Column(
1631 "locked", String(255), nullable=True, unique=False, default=None)
1631 "locked", String(255), nullable=True, unique=False, default=None)
1632 _changeset_cache = Column(
1632 _changeset_cache = Column(
1633 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1633 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1634
1634
1635 fork_id = Column(
1635 fork_id = Column(
1636 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1636 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1637 nullable=True, unique=False, default=None)
1637 nullable=True, unique=False, default=None)
1638 group_id = Column(
1638 group_id = Column(
1639 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1639 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1640 unique=False, default=None)
1640 unique=False, default=None)
1641
1641
1642 user = relationship('User', lazy='joined')
1642 user = relationship('User', lazy='joined')
1643 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1643 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1644 group = relationship('RepoGroup', lazy='joined')
1644 group = relationship('RepoGroup', lazy='joined')
1645 repo_to_perm = relationship(
1645 repo_to_perm = relationship(
1646 'UserRepoToPerm', cascade='all',
1646 'UserRepoToPerm', cascade='all',
1647 order_by='UserRepoToPerm.repo_to_perm_id')
1647 order_by='UserRepoToPerm.repo_to_perm_id')
1648 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1648 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1649 stats = relationship('Statistics', cascade='all', uselist=False)
1649 stats = relationship('Statistics', cascade='all', uselist=False)
1650
1650
1651 followers = relationship(
1651 followers = relationship(
1652 'UserFollowing',
1652 'UserFollowing',
1653 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1653 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1654 cascade='all')
1654 cascade='all')
1655 extra_fields = relationship(
1655 extra_fields = relationship(
1656 'RepositoryField', cascade="all, delete, delete-orphan")
1656 'RepositoryField', cascade="all, delete, delete-orphan")
1657 logs = relationship('UserLog')
1657 logs = relationship('UserLog')
1658 comments = relationship(
1658 comments = relationship(
1659 'ChangesetComment', cascade="all, delete, delete-orphan")
1659 'ChangesetComment', cascade="all, delete, delete-orphan")
1660 pull_requests_source = relationship(
1660 pull_requests_source = relationship(
1661 'PullRequest',
1661 'PullRequest',
1662 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1662 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1663 cascade="all, delete, delete-orphan")
1663 cascade="all, delete, delete-orphan")
1664 pull_requests_target = relationship(
1664 pull_requests_target = relationship(
1665 'PullRequest',
1665 'PullRequest',
1666 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1666 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1667 cascade="all, delete, delete-orphan")
1667 cascade="all, delete, delete-orphan")
1668 ui = relationship('RepoRhodeCodeUi', cascade="all")
1668 ui = relationship('RepoRhodeCodeUi', cascade="all")
1669 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1669 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1670 integrations = relationship('Integration',
1670 integrations = relationship('Integration',
1671 cascade="all, delete, delete-orphan")
1671 cascade="all, delete, delete-orphan")
1672
1672
1673 scoped_tokens = relationship('UserApiKeys', cascade="all")
1673 scoped_tokens = relationship('UserApiKeys', cascade="all")
1674
1674
1675 def __unicode__(self):
1675 def __unicode__(self):
1676 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1676 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1677 safe_unicode(self.repo_name))
1677 safe_unicode(self.repo_name))
1678
1678
1679 @hybrid_property
1679 @hybrid_property
1680 def description_safe(self):
1680 def description_safe(self):
1681 from rhodecode.lib import helpers as h
1681 from rhodecode.lib import helpers as h
1682 return h.escape(self.description)
1682 return h.escape(self.description)
1683
1683
1684 @hybrid_property
1684 @hybrid_property
1685 def landing_rev(self):
1685 def landing_rev(self):
1686 # always should return [rev_type, rev]
1686 # always should return [rev_type, rev]
1687 if self._landing_revision:
1687 if self._landing_revision:
1688 _rev_info = self._landing_revision.split(':')
1688 _rev_info = self._landing_revision.split(':')
1689 if len(_rev_info) < 2:
1689 if len(_rev_info) < 2:
1690 _rev_info.insert(0, 'rev')
1690 _rev_info.insert(0, 'rev')
1691 return [_rev_info[0], _rev_info[1]]
1691 return [_rev_info[0], _rev_info[1]]
1692 return [None, None]
1692 return [None, None]
1693
1693
1694 @landing_rev.setter
1694 @landing_rev.setter
1695 def landing_rev(self, val):
1695 def landing_rev(self, val):
1696 if ':' not in val:
1696 if ':' not in val:
1697 raise ValueError('value must be delimited with `:` and consist '
1697 raise ValueError('value must be delimited with `:` and consist '
1698 'of <rev_type>:<rev>, got %s instead' % val)
1698 'of <rev_type>:<rev>, got %s instead' % val)
1699 self._landing_revision = val
1699 self._landing_revision = val
1700
1700
1701 @hybrid_property
1701 @hybrid_property
1702 def locked(self):
1702 def locked(self):
1703 if self._locked:
1703 if self._locked:
1704 user_id, timelocked, reason = self._locked.split(':')
1704 user_id, timelocked, reason = self._locked.split(':')
1705 lock_values = int(user_id), timelocked, reason
1705 lock_values = int(user_id), timelocked, reason
1706 else:
1706 else:
1707 lock_values = [None, None, None]
1707 lock_values = [None, None, None]
1708 return lock_values
1708 return lock_values
1709
1709
1710 @locked.setter
1710 @locked.setter
1711 def locked(self, val):
1711 def locked(self, val):
1712 if val and isinstance(val, (list, tuple)):
1712 if val and isinstance(val, (list, tuple)):
1713 self._locked = ':'.join(map(str, val))
1713 self._locked = ':'.join(map(str, val))
1714 else:
1714 else:
1715 self._locked = None
1715 self._locked = None
1716
1716
1717 @hybrid_property
1717 @hybrid_property
1718 def changeset_cache(self):
1718 def changeset_cache(self):
1719 from rhodecode.lib.vcs.backends.base import EmptyCommit
1719 from rhodecode.lib.vcs.backends.base import EmptyCommit
1720 dummy = EmptyCommit().__json__()
1720 dummy = EmptyCommit().__json__()
1721 if not self._changeset_cache:
1721 if not self._changeset_cache:
1722 return dummy
1722 return dummy
1723 try:
1723 try:
1724 return json.loads(self._changeset_cache)
1724 return json.loads(self._changeset_cache)
1725 except TypeError:
1725 except TypeError:
1726 return dummy
1726 return dummy
1727 except Exception:
1727 except Exception:
1728 log.error(traceback.format_exc())
1728 log.error(traceback.format_exc())
1729 return dummy
1729 return dummy
1730
1730
1731 @changeset_cache.setter
1731 @changeset_cache.setter
1732 def changeset_cache(self, val):
1732 def changeset_cache(self, val):
1733 try:
1733 try:
1734 self._changeset_cache = json.dumps(val)
1734 self._changeset_cache = json.dumps(val)
1735 except Exception:
1735 except Exception:
1736 log.error(traceback.format_exc())
1736 log.error(traceback.format_exc())
1737
1737
1738 @hybrid_property
1738 @hybrid_property
1739 def repo_name(self):
1739 def repo_name(self):
1740 return self._repo_name
1740 return self._repo_name
1741
1741
1742 @repo_name.setter
1742 @repo_name.setter
1743 def repo_name(self, value):
1743 def repo_name(self, value):
1744 self._repo_name = value
1744 self._repo_name = value
1745 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1745 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1746
1746
1747 @classmethod
1747 @classmethod
1748 def normalize_repo_name(cls, repo_name):
1748 def normalize_repo_name(cls, repo_name):
1749 """
1749 """
1750 Normalizes os specific repo_name to the format internally stored inside
1750 Normalizes os specific repo_name to the format internally stored inside
1751 database using URL_SEP
1751 database using URL_SEP
1752
1752
1753 :param cls:
1753 :param cls:
1754 :param repo_name:
1754 :param repo_name:
1755 """
1755 """
1756 return cls.NAME_SEP.join(repo_name.split(os.sep))
1756 return cls.NAME_SEP.join(repo_name.split(os.sep))
1757
1757
1758 @classmethod
1758 @classmethod
1759 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1759 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1760 session = Session()
1760 session = Session()
1761 q = session.query(cls).filter(cls.repo_name == repo_name)
1761 q = session.query(cls).filter(cls.repo_name == repo_name)
1762
1762
1763 if cache:
1763 if cache:
1764 if identity_cache:
1764 if identity_cache:
1765 val = cls.identity_cache(session, 'repo_name', repo_name)
1765 val = cls.identity_cache(session, 'repo_name', repo_name)
1766 if val:
1766 if val:
1767 return val
1767 return val
1768 else:
1768 else:
1769 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1769 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1770 q = q.options(
1770 q = q.options(
1771 FromCache("sql_cache_short", cache_key))
1771 FromCache("sql_cache_short", cache_key))
1772
1772
1773 return q.scalar()
1773 return q.scalar()
1774
1774
1775 @classmethod
1775 @classmethod
1776 def get_by_id_or_repo_name(cls, repoid):
1776 def get_by_id_or_repo_name(cls, repoid):
1777 if isinstance(repoid, (int, long)):
1777 if isinstance(repoid, (int, long)):
1778 try:
1778 try:
1779 repo = cls.get(repoid)
1779 repo = cls.get(repoid)
1780 except ValueError:
1780 except ValueError:
1781 repo = None
1781 repo = None
1782 else:
1782 else:
1783 repo = cls.get_by_repo_name(repoid)
1783 repo = cls.get_by_repo_name(repoid)
1784 return repo
1784 return repo
1785
1785
1786 @classmethod
1786 @classmethod
1787 def get_by_full_path(cls, repo_full_path):
1787 def get_by_full_path(cls, repo_full_path):
1788 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1788 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1789 repo_name = cls.normalize_repo_name(repo_name)
1789 repo_name = cls.normalize_repo_name(repo_name)
1790 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1790 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1791
1791
1792 @classmethod
1792 @classmethod
1793 def get_repo_forks(cls, repo_id):
1793 def get_repo_forks(cls, repo_id):
1794 return cls.query().filter(Repository.fork_id == repo_id)
1794 return cls.query().filter(Repository.fork_id == repo_id)
1795
1795
1796 @classmethod
1796 @classmethod
1797 def base_path(cls):
1797 def base_path(cls):
1798 """
1798 """
1799 Returns base path when all repos are stored
1799 Returns base path when all repos are stored
1800
1800
1801 :param cls:
1801 :param cls:
1802 """
1802 """
1803 q = Session().query(RhodeCodeUi)\
1803 q = Session().query(RhodeCodeUi)\
1804 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1804 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1805 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1805 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1806 return q.one().ui_value
1806 return q.one().ui_value
1807
1807
1808 @classmethod
1808 @classmethod
1809 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1809 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1810 case_insensitive=True, archived=False):
1810 case_insensitive=True, archived=False):
1811 q = Repository.query()
1811 q = Repository.query()
1812
1812
1813 if not archived:
1813 if not archived:
1814 q = q.filter(Repository.archived.isnot(true()))
1814 q = q.filter(Repository.archived.isnot(true()))
1815
1815
1816 if not isinstance(user_id, Optional):
1816 if not isinstance(user_id, Optional):
1817 q = q.filter(Repository.user_id == user_id)
1817 q = q.filter(Repository.user_id == user_id)
1818
1818
1819 if not isinstance(group_id, Optional):
1819 if not isinstance(group_id, Optional):
1820 q = q.filter(Repository.group_id == group_id)
1820 q = q.filter(Repository.group_id == group_id)
1821
1821
1822 if case_insensitive:
1822 if case_insensitive:
1823 q = q.order_by(func.lower(Repository.repo_name))
1823 q = q.order_by(func.lower(Repository.repo_name))
1824 else:
1824 else:
1825 q = q.order_by(Repository.repo_name)
1825 q = q.order_by(Repository.repo_name)
1826
1826
1827 return q.all()
1827 return q.all()
1828
1828
1829 @property
1829 @property
1830 def forks(self):
1830 def forks(self):
1831 """
1831 """
1832 Return forks of this repo
1832 Return forks of this repo
1833 """
1833 """
1834 return Repository.get_repo_forks(self.repo_id)
1834 return Repository.get_repo_forks(self.repo_id)
1835
1835
1836 @property
1836 @property
1837 def parent(self):
1837 def parent(self):
1838 """
1838 """
1839 Returns fork parent
1839 Returns fork parent
1840 """
1840 """
1841 return self.fork
1841 return self.fork
1842
1842
1843 @property
1843 @property
1844 def just_name(self):
1844 def just_name(self):
1845 return self.repo_name.split(self.NAME_SEP)[-1]
1845 return self.repo_name.split(self.NAME_SEP)[-1]
1846
1846
1847 @property
1847 @property
1848 def groups_with_parents(self):
1848 def groups_with_parents(self):
1849 groups = []
1849 groups = []
1850 if self.group is None:
1850 if self.group is None:
1851 return groups
1851 return groups
1852
1852
1853 cur_gr = self.group
1853 cur_gr = self.group
1854 groups.insert(0, cur_gr)
1854 groups.insert(0, cur_gr)
1855 while 1:
1855 while 1:
1856 gr = getattr(cur_gr, 'parent_group', None)
1856 gr = getattr(cur_gr, 'parent_group', None)
1857 cur_gr = cur_gr.parent_group
1857 cur_gr = cur_gr.parent_group
1858 if gr is None:
1858 if gr is None:
1859 break
1859 break
1860 groups.insert(0, gr)
1860 groups.insert(0, gr)
1861
1861
1862 return groups
1862 return groups
1863
1863
1864 @property
1864 @property
1865 def groups_and_repo(self):
1865 def groups_and_repo(self):
1866 return self.groups_with_parents, self
1866 return self.groups_with_parents, self
1867
1867
1868 @LazyProperty
1868 @LazyProperty
1869 def repo_path(self):
1869 def repo_path(self):
1870 """
1870 """
1871 Returns base full path for that repository means where it actually
1871 Returns base full path for that repository means where it actually
1872 exists on a filesystem
1872 exists on a filesystem
1873 """
1873 """
1874 q = Session().query(RhodeCodeUi).filter(
1874 q = Session().query(RhodeCodeUi).filter(
1875 RhodeCodeUi.ui_key == self.NAME_SEP)
1875 RhodeCodeUi.ui_key == self.NAME_SEP)
1876 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1876 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1877 return q.one().ui_value
1877 return q.one().ui_value
1878
1878
1879 @property
1879 @property
1880 def repo_full_path(self):
1880 def repo_full_path(self):
1881 p = [self.repo_path]
1881 p = [self.repo_path]
1882 # we need to split the name by / since this is how we store the
1882 # we need to split the name by / since this is how we store the
1883 # names in the database, but that eventually needs to be converted
1883 # names in the database, but that eventually needs to be converted
1884 # into a valid system path
1884 # into a valid system path
1885 p += self.repo_name.split(self.NAME_SEP)
1885 p += self.repo_name.split(self.NAME_SEP)
1886 return os.path.join(*map(safe_unicode, p))
1886 return os.path.join(*map(safe_unicode, p))
1887
1887
1888 @property
1888 @property
1889 def cache_keys(self):
1889 def cache_keys(self):
1890 """
1890 """
1891 Returns associated cache keys for that repo
1891 Returns associated cache keys for that repo
1892 """
1892 """
1893 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1893 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1894 repo_id=self.repo_id)
1894 repo_id=self.repo_id)
1895 return CacheKey.query()\
1895 return CacheKey.query()\
1896 .filter(CacheKey.cache_args == invalidation_namespace)\
1896 .filter(CacheKey.cache_args == invalidation_namespace)\
1897 .order_by(CacheKey.cache_key)\
1897 .order_by(CacheKey.cache_key)\
1898 .all()
1898 .all()
1899
1899
1900 @property
1900 @property
1901 def cached_diffs_relative_dir(self):
1901 def cached_diffs_relative_dir(self):
1902 """
1902 """
1903 Return a relative to the repository store path of cached diffs
1903 Return a relative to the repository store path of cached diffs
1904 used for safe display for users, who shouldn't know the absolute store
1904 used for safe display for users, who shouldn't know the absolute store
1905 path
1905 path
1906 """
1906 """
1907 return os.path.join(
1907 return os.path.join(
1908 os.path.dirname(self.repo_name),
1908 os.path.dirname(self.repo_name),
1909 self.cached_diffs_dir.split(os.path.sep)[-1])
1909 self.cached_diffs_dir.split(os.path.sep)[-1])
1910
1910
1911 @property
1911 @property
1912 def cached_diffs_dir(self):
1912 def cached_diffs_dir(self):
1913 path = self.repo_full_path
1913 path = self.repo_full_path
1914 return os.path.join(
1914 return os.path.join(
1915 os.path.dirname(path),
1915 os.path.dirname(path),
1916 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1916 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1917
1917
1918 def cached_diffs(self):
1918 def cached_diffs(self):
1919 diff_cache_dir = self.cached_diffs_dir
1919 diff_cache_dir = self.cached_diffs_dir
1920 if os.path.isdir(diff_cache_dir):
1920 if os.path.isdir(diff_cache_dir):
1921 return os.listdir(diff_cache_dir)
1921 return os.listdir(diff_cache_dir)
1922 return []
1922 return []
1923
1923
1924 def shadow_repos(self):
1924 def shadow_repos(self):
1925 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1925 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1926 return [
1926 return [
1927 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1927 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1928 if x.startswith(shadow_repos_pattern)]
1928 if x.startswith(shadow_repos_pattern)]
1929
1929
1930 def get_new_name(self, repo_name):
1930 def get_new_name(self, repo_name):
1931 """
1931 """
1932 returns new full repository name based on assigned group and new new
1932 returns new full repository name based on assigned group and new new
1933
1933
1934 :param group_name:
1934 :param group_name:
1935 """
1935 """
1936 path_prefix = self.group.full_path_splitted if self.group else []
1936 path_prefix = self.group.full_path_splitted if self.group else []
1937 return self.NAME_SEP.join(path_prefix + [repo_name])
1937 return self.NAME_SEP.join(path_prefix + [repo_name])
1938
1938
1939 @property
1939 @property
1940 def _config(self):
1940 def _config(self):
1941 """
1941 """
1942 Returns db based config object.
1942 Returns db based config object.
1943 """
1943 """
1944 from rhodecode.lib.utils import make_db_config
1944 from rhodecode.lib.utils import make_db_config
1945 return make_db_config(clear_session=False, repo=self)
1945 return make_db_config(clear_session=False, repo=self)
1946
1946
1947 def permissions(self, with_admins=True, with_owner=True,
1947 def permissions(self, with_admins=True, with_owner=True,
1948 expand_from_user_groups=False):
1948 expand_from_user_groups=False):
1949 """
1949 """
1950 Permissions for repositories
1950 Permissions for repositories
1951 """
1951 """
1952 _admin_perm = 'repository.admin'
1952 _admin_perm = 'repository.admin'
1953
1953
1954 owner_row = []
1954 owner_row = []
1955 if with_owner:
1955 if with_owner:
1956 usr = AttributeDict(self.user.get_dict())
1956 usr = AttributeDict(self.user.get_dict())
1957 usr.owner_row = True
1957 usr.owner_row = True
1958 usr.permission = _admin_perm
1958 usr.permission = _admin_perm
1959 usr.permission_id = None
1959 usr.permission_id = None
1960 owner_row.append(usr)
1960 owner_row.append(usr)
1961
1961
1962 super_admin_ids = []
1962 super_admin_ids = []
1963 super_admin_rows = []
1963 super_admin_rows = []
1964 if with_admins:
1964 if with_admins:
1965 for usr in User.get_all_super_admins():
1965 for usr in User.get_all_super_admins():
1966 super_admin_ids.append(usr.user_id)
1966 super_admin_ids.append(usr.user_id)
1967 # if this admin is also owner, don't double the record
1967 # if this admin is also owner, don't double the record
1968 if usr.user_id == owner_row[0].user_id:
1968 if usr.user_id == owner_row[0].user_id:
1969 owner_row[0].admin_row = True
1969 owner_row[0].admin_row = True
1970 else:
1970 else:
1971 usr = AttributeDict(usr.get_dict())
1971 usr = AttributeDict(usr.get_dict())
1972 usr.admin_row = True
1972 usr.admin_row = True
1973 usr.permission = _admin_perm
1973 usr.permission = _admin_perm
1974 usr.permission_id = None
1974 usr.permission_id = None
1975 super_admin_rows.append(usr)
1975 super_admin_rows.append(usr)
1976
1976
1977 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1977 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1978 q = q.options(joinedload(UserRepoToPerm.repository),
1978 q = q.options(joinedload(UserRepoToPerm.repository),
1979 joinedload(UserRepoToPerm.user),
1979 joinedload(UserRepoToPerm.user),
1980 joinedload(UserRepoToPerm.permission),)
1980 joinedload(UserRepoToPerm.permission),)
1981
1981
1982 # get owners and admins and permissions. We do a trick of re-writing
1982 # get owners and admins and permissions. We do a trick of re-writing
1983 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1983 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1984 # has a global reference and changing one object propagates to all
1984 # has a global reference and changing one object propagates to all
1985 # others. This means if admin is also an owner admin_row that change
1985 # others. This means if admin is also an owner admin_row that change
1986 # would propagate to both objects
1986 # would propagate to both objects
1987 perm_rows = []
1987 perm_rows = []
1988 for _usr in q.all():
1988 for _usr in q.all():
1989 usr = AttributeDict(_usr.user.get_dict())
1989 usr = AttributeDict(_usr.user.get_dict())
1990 # if this user is also owner/admin, mark as duplicate record
1990 # if this user is also owner/admin, mark as duplicate record
1991 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1991 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1992 usr.duplicate_perm = True
1992 usr.duplicate_perm = True
1993 # also check if this permission is maybe used by branch_permissions
1993 # also check if this permission is maybe used by branch_permissions
1994 if _usr.branch_perm_entry:
1994 if _usr.branch_perm_entry:
1995 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1995 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1996
1996
1997 usr.permission = _usr.permission.permission_name
1997 usr.permission = _usr.permission.permission_name
1998 usr.permission_id = _usr.repo_to_perm_id
1998 usr.permission_id = _usr.repo_to_perm_id
1999 perm_rows.append(usr)
1999 perm_rows.append(usr)
2000
2000
2001 # filter the perm rows by 'default' first and then sort them by
2001 # filter the perm rows by 'default' first and then sort them by
2002 # admin,write,read,none permissions sorted again alphabetically in
2002 # admin,write,read,none permissions sorted again alphabetically in
2003 # each group
2003 # each group
2004 perm_rows = sorted(perm_rows, key=display_user_sort)
2004 perm_rows = sorted(perm_rows, key=display_user_sort)
2005
2005
2006 user_groups_rows = []
2006 user_groups_rows = []
2007 if expand_from_user_groups:
2007 if expand_from_user_groups:
2008 for ug in self.permission_user_groups(with_members=True):
2008 for ug in self.permission_user_groups(with_members=True):
2009 for user_data in ug.members:
2009 for user_data in ug.members:
2010 user_groups_rows.append(user_data)
2010 user_groups_rows.append(user_data)
2011
2011
2012 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2012 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2013
2013
2014 def permission_user_groups(self, with_members=True):
2014 def permission_user_groups(self, with_members=True):
2015 q = UserGroupRepoToPerm.query()\
2015 q = UserGroupRepoToPerm.query()\
2016 .filter(UserGroupRepoToPerm.repository == self)
2016 .filter(UserGroupRepoToPerm.repository == self)
2017 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2017 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2018 joinedload(UserGroupRepoToPerm.users_group),
2018 joinedload(UserGroupRepoToPerm.users_group),
2019 joinedload(UserGroupRepoToPerm.permission),)
2019 joinedload(UserGroupRepoToPerm.permission),)
2020
2020
2021 perm_rows = []
2021 perm_rows = []
2022 for _user_group in q.all():
2022 for _user_group in q.all():
2023 entry = AttributeDict(_user_group.users_group.get_dict())
2023 entry = AttributeDict(_user_group.users_group.get_dict())
2024 entry.permission = _user_group.permission.permission_name
2024 entry.permission = _user_group.permission.permission_name
2025 if with_members:
2025 if with_members:
2026 entry.members = [x.user.get_dict()
2026 entry.members = [x.user.get_dict()
2027 for x in _user_group.users_group.members]
2027 for x in _user_group.users_group.members]
2028 perm_rows.append(entry)
2028 perm_rows.append(entry)
2029
2029
2030 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2030 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2031 return perm_rows
2031 return perm_rows
2032
2032
2033 def get_api_data(self, include_secrets=False):
2033 def get_api_data(self, include_secrets=False):
2034 """
2034 """
2035 Common function for generating repo api data
2035 Common function for generating repo api data
2036
2036
2037 :param include_secrets: See :meth:`User.get_api_data`.
2037 :param include_secrets: See :meth:`User.get_api_data`.
2038
2038
2039 """
2039 """
2040 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2040 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2041 # move this methods on models level.
2041 # move this methods on models level.
2042 from rhodecode.model.settings import SettingsModel
2042 from rhodecode.model.settings import SettingsModel
2043 from rhodecode.model.repo import RepoModel
2043 from rhodecode.model.repo import RepoModel
2044
2044
2045 repo = self
2045 repo = self
2046 _user_id, _time, _reason = self.locked
2046 _user_id, _time, _reason = self.locked
2047
2047
2048 data = {
2048 data = {
2049 'repo_id': repo.repo_id,
2049 'repo_id': repo.repo_id,
2050 'repo_name': repo.repo_name,
2050 'repo_name': repo.repo_name,
2051 'repo_type': repo.repo_type,
2051 'repo_type': repo.repo_type,
2052 'clone_uri': repo.clone_uri or '',
2052 'clone_uri': repo.clone_uri or '',
2053 'push_uri': repo.push_uri or '',
2053 'push_uri': repo.push_uri or '',
2054 'url': RepoModel().get_url(self),
2054 'url': RepoModel().get_url(self),
2055 'private': repo.private,
2055 'private': repo.private,
2056 'created_on': repo.created_on,
2056 'created_on': repo.created_on,
2057 'description': repo.description_safe,
2057 'description': repo.description_safe,
2058 'landing_rev': repo.landing_rev,
2058 'landing_rev': repo.landing_rev,
2059 'owner': repo.user.username,
2059 'owner': repo.user.username,
2060 'fork_of': repo.fork.repo_name if repo.fork else None,
2060 'fork_of': repo.fork.repo_name if repo.fork else None,
2061 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2061 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2062 'enable_statistics': repo.enable_statistics,
2062 'enable_statistics': repo.enable_statistics,
2063 'enable_locking': repo.enable_locking,
2063 'enable_locking': repo.enable_locking,
2064 'enable_downloads': repo.enable_downloads,
2064 'enable_downloads': repo.enable_downloads,
2065 'last_changeset': repo.changeset_cache,
2065 'last_changeset': repo.changeset_cache,
2066 'locked_by': User.get(_user_id).get_api_data(
2066 'locked_by': User.get(_user_id).get_api_data(
2067 include_secrets=include_secrets) if _user_id else None,
2067 include_secrets=include_secrets) if _user_id else None,
2068 'locked_date': time_to_datetime(_time) if _time else None,
2068 'locked_date': time_to_datetime(_time) if _time else None,
2069 'lock_reason': _reason if _reason else None,
2069 'lock_reason': _reason if _reason else None,
2070 }
2070 }
2071
2071
2072 # TODO: mikhail: should be per-repo settings here
2072 # TODO: mikhail: should be per-repo settings here
2073 rc_config = SettingsModel().get_all_settings()
2073 rc_config = SettingsModel().get_all_settings()
2074 repository_fields = str2bool(
2074 repository_fields = str2bool(
2075 rc_config.get('rhodecode_repository_fields'))
2075 rc_config.get('rhodecode_repository_fields'))
2076 if repository_fields:
2076 if repository_fields:
2077 for f in self.extra_fields:
2077 for f in self.extra_fields:
2078 data[f.field_key_prefixed] = f.field_value
2078 data[f.field_key_prefixed] = f.field_value
2079
2079
2080 return data
2080 return data
2081
2081
2082 @classmethod
2082 @classmethod
2083 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2083 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2084 if not lock_time:
2084 if not lock_time:
2085 lock_time = time.time()
2085 lock_time = time.time()
2086 if not lock_reason:
2086 if not lock_reason:
2087 lock_reason = cls.LOCK_AUTOMATIC
2087 lock_reason = cls.LOCK_AUTOMATIC
2088 repo.locked = [user_id, lock_time, lock_reason]
2088 repo.locked = [user_id, lock_time, lock_reason]
2089 Session().add(repo)
2089 Session().add(repo)
2090 Session().commit()
2090 Session().commit()
2091
2091
2092 @classmethod
2092 @classmethod
2093 def unlock(cls, repo):
2093 def unlock(cls, repo):
2094 repo.locked = None
2094 repo.locked = None
2095 Session().add(repo)
2095 Session().add(repo)
2096 Session().commit()
2096 Session().commit()
2097
2097
2098 @classmethod
2098 @classmethod
2099 def getlock(cls, repo):
2099 def getlock(cls, repo):
2100 return repo.locked
2100 return repo.locked
2101
2101
2102 def is_user_lock(self, user_id):
2102 def is_user_lock(self, user_id):
2103 if self.lock[0]:
2103 if self.lock[0]:
2104 lock_user_id = safe_int(self.lock[0])
2104 lock_user_id = safe_int(self.lock[0])
2105 user_id = safe_int(user_id)
2105 user_id = safe_int(user_id)
2106 # both are ints, and they are equal
2106 # both are ints, and they are equal
2107 return all([lock_user_id, user_id]) and lock_user_id == user_id
2107 return all([lock_user_id, user_id]) and lock_user_id == user_id
2108
2108
2109 return False
2109 return False
2110
2110
2111 def get_locking_state(self, action, user_id, only_when_enabled=True):
2111 def get_locking_state(self, action, user_id, only_when_enabled=True):
2112 """
2112 """
2113 Checks locking on this repository, if locking is enabled and lock is
2113 Checks locking on this repository, if locking is enabled and lock is
2114 present returns a tuple of make_lock, locked, locked_by.
2114 present returns a tuple of make_lock, locked, locked_by.
2115 make_lock can have 3 states None (do nothing) True, make lock
2115 make_lock can have 3 states None (do nothing) True, make lock
2116 False release lock, This value is later propagated to hooks, which
2116 False release lock, This value is later propagated to hooks, which
2117 do the locking. Think about this as signals passed to hooks what to do.
2117 do the locking. Think about this as signals passed to hooks what to do.
2118
2118
2119 """
2119 """
2120 # TODO: johbo: This is part of the business logic and should be moved
2120 # TODO: johbo: This is part of the business logic and should be moved
2121 # into the RepositoryModel.
2121 # into the RepositoryModel.
2122
2122
2123 if action not in ('push', 'pull'):
2123 if action not in ('push', 'pull'):
2124 raise ValueError("Invalid action value: %s" % repr(action))
2124 raise ValueError("Invalid action value: %s" % repr(action))
2125
2125
2126 # defines if locked error should be thrown to user
2126 # defines if locked error should be thrown to user
2127 currently_locked = False
2127 currently_locked = False
2128 # defines if new lock should be made, tri-state
2128 # defines if new lock should be made, tri-state
2129 make_lock = None
2129 make_lock = None
2130 repo = self
2130 repo = self
2131 user = User.get(user_id)
2131 user = User.get(user_id)
2132
2132
2133 lock_info = repo.locked
2133 lock_info = repo.locked
2134
2134
2135 if repo and (repo.enable_locking or not only_when_enabled):
2135 if repo and (repo.enable_locking or not only_when_enabled):
2136 if action == 'push':
2136 if action == 'push':
2137 # check if it's already locked !, if it is compare users
2137 # check if it's already locked !, if it is compare users
2138 locked_by_user_id = lock_info[0]
2138 locked_by_user_id = lock_info[0]
2139 if user.user_id == locked_by_user_id:
2139 if user.user_id == locked_by_user_id:
2140 log.debug(
2140 log.debug(
2141 'Got `push` action from user %s, now unlocking', user)
2141 'Got `push` action from user %s, now unlocking', user)
2142 # unlock if we have push from user who locked
2142 # unlock if we have push from user who locked
2143 make_lock = False
2143 make_lock = False
2144 else:
2144 else:
2145 # we're not the same user who locked, ban with
2145 # we're not the same user who locked, ban with
2146 # code defined in settings (default is 423 HTTP Locked) !
2146 # code defined in settings (default is 423 HTTP Locked) !
2147 log.debug('Repo %s is currently locked by %s', repo, user)
2147 log.debug('Repo %s is currently locked by %s', repo, user)
2148 currently_locked = True
2148 currently_locked = True
2149 elif action == 'pull':
2149 elif action == 'pull':
2150 # [0] user [1] date
2150 # [0] user [1] date
2151 if lock_info[0] and lock_info[1]:
2151 if lock_info[0] and lock_info[1]:
2152 log.debug('Repo %s is currently locked by %s', repo, user)
2152 log.debug('Repo %s is currently locked by %s', repo, user)
2153 currently_locked = True
2153 currently_locked = True
2154 else:
2154 else:
2155 log.debug('Setting lock on repo %s by %s', repo, user)
2155 log.debug('Setting lock on repo %s by %s', repo, user)
2156 make_lock = True
2156 make_lock = True
2157
2157
2158 else:
2158 else:
2159 log.debug('Repository %s do not have locking enabled', repo)
2159 log.debug('Repository %s do not have locking enabled', repo)
2160
2160
2161 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2161 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2162 make_lock, currently_locked, lock_info)
2162 make_lock, currently_locked, lock_info)
2163
2163
2164 from rhodecode.lib.auth import HasRepoPermissionAny
2164 from rhodecode.lib.auth import HasRepoPermissionAny
2165 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2165 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2166 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2166 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2167 # if we don't have at least write permission we cannot make a lock
2167 # if we don't have at least write permission we cannot make a lock
2168 log.debug('lock state reset back to FALSE due to lack '
2168 log.debug('lock state reset back to FALSE due to lack '
2169 'of at least read permission')
2169 'of at least read permission')
2170 make_lock = False
2170 make_lock = False
2171
2171
2172 return make_lock, currently_locked, lock_info
2172 return make_lock, currently_locked, lock_info
2173
2173
2174 @property
2174 @property
2175 def last_db_change(self):
2175 def last_db_change(self):
2176 return self.updated_on
2176 return self.updated_on
2177
2177
2178 @property
2178 @property
2179 def clone_uri_hidden(self):
2179 def clone_uri_hidden(self):
2180 clone_uri = self.clone_uri
2180 clone_uri = self.clone_uri
2181 if clone_uri:
2181 if clone_uri:
2182 import urlobject
2182 import urlobject
2183 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2183 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2184 if url_obj.password:
2184 if url_obj.password:
2185 clone_uri = url_obj.with_password('*****')
2185 clone_uri = url_obj.with_password('*****')
2186 return clone_uri
2186 return clone_uri
2187
2187
2188 @property
2188 @property
2189 def push_uri_hidden(self):
2189 def push_uri_hidden(self):
2190 push_uri = self.push_uri
2190 push_uri = self.push_uri
2191 if push_uri:
2191 if push_uri:
2192 import urlobject
2192 import urlobject
2193 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2193 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2194 if url_obj.password:
2194 if url_obj.password:
2195 push_uri = url_obj.with_password('*****')
2195 push_uri = url_obj.with_password('*****')
2196 return push_uri
2196 return push_uri
2197
2197
2198 def clone_url(self, **override):
2198 def clone_url(self, **override):
2199 from rhodecode.model.settings import SettingsModel
2199 from rhodecode.model.settings import SettingsModel
2200
2200
2201 uri_tmpl = None
2201 uri_tmpl = None
2202 if 'with_id' in override:
2202 if 'with_id' in override:
2203 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2203 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2204 del override['with_id']
2204 del override['with_id']
2205
2205
2206 if 'uri_tmpl' in override:
2206 if 'uri_tmpl' in override:
2207 uri_tmpl = override['uri_tmpl']
2207 uri_tmpl = override['uri_tmpl']
2208 del override['uri_tmpl']
2208 del override['uri_tmpl']
2209
2209
2210 ssh = False
2210 ssh = False
2211 if 'ssh' in override:
2211 if 'ssh' in override:
2212 ssh = True
2212 ssh = True
2213 del override['ssh']
2213 del override['ssh']
2214
2214
2215 # we didn't override our tmpl from **overrides
2215 # we didn't override our tmpl from **overrides
2216 if not uri_tmpl:
2216 if not uri_tmpl:
2217 rc_config = SettingsModel().get_all_settings(cache=True)
2217 rc_config = SettingsModel().get_all_settings(cache=True)
2218 if ssh:
2218 if ssh:
2219 uri_tmpl = rc_config.get(
2219 uri_tmpl = rc_config.get(
2220 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2220 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2221 else:
2221 else:
2222 uri_tmpl = rc_config.get(
2222 uri_tmpl = rc_config.get(
2223 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2223 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2224
2224
2225 request = get_current_request()
2225 request = get_current_request()
2226 return get_clone_url(request=request,
2226 return get_clone_url(request=request,
2227 uri_tmpl=uri_tmpl,
2227 uri_tmpl=uri_tmpl,
2228 repo_name=self.repo_name,
2228 repo_name=self.repo_name,
2229 repo_id=self.repo_id, **override)
2229 repo_id=self.repo_id, **override)
2230
2230
2231 def set_state(self, state):
2231 def set_state(self, state):
2232 self.repo_state = state
2232 self.repo_state = state
2233 Session().add(self)
2233 Session().add(self)
2234 #==========================================================================
2234 #==========================================================================
2235 # SCM PROPERTIES
2235 # SCM PROPERTIES
2236 #==========================================================================
2236 #==========================================================================
2237
2237
2238 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2238 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2239 return get_commit_safe(
2239 return get_commit_safe(
2240 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2240 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2241
2241
2242 def get_changeset(self, rev=None, pre_load=None):
2242 def get_changeset(self, rev=None, pre_load=None):
2243 warnings.warn("Use get_commit", DeprecationWarning)
2243 warnings.warn("Use get_commit", DeprecationWarning)
2244 commit_id = None
2244 commit_id = None
2245 commit_idx = None
2245 commit_idx = None
2246 if isinstance(rev, compat.string_types):
2246 if isinstance(rev, compat.string_types):
2247 commit_id = rev
2247 commit_id = rev
2248 else:
2248 else:
2249 commit_idx = rev
2249 commit_idx = rev
2250 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2250 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2251 pre_load=pre_load)
2251 pre_load=pre_load)
2252
2252
2253 def get_landing_commit(self):
2253 def get_landing_commit(self):
2254 """
2254 """
2255 Returns landing commit, or if that doesn't exist returns the tip
2255 Returns landing commit, or if that doesn't exist returns the tip
2256 """
2256 """
2257 _rev_type, _rev = self.landing_rev
2257 _rev_type, _rev = self.landing_rev
2258 commit = self.get_commit(_rev)
2258 commit = self.get_commit(_rev)
2259 if isinstance(commit, EmptyCommit):
2259 if isinstance(commit, EmptyCommit):
2260 return self.get_commit()
2260 return self.get_commit()
2261 return commit
2261 return commit
2262
2262
2263 def update_commit_cache(self, cs_cache=None, config=None):
2263 def update_commit_cache(self, cs_cache=None, config=None):
2264 """
2264 """
2265 Update cache of last changeset for repository, keys should be::
2265 Update cache of last changeset for repository, keys should be::
2266
2266
2267 short_id
2267 short_id
2268 raw_id
2268 raw_id
2269 revision
2269 revision
2270 parents
2270 parents
2271 message
2271 message
2272 date
2272 date
2273 author
2273 author
2274
2274
2275 :param cs_cache:
2275 :param cs_cache:
2276 """
2276 """
2277 from rhodecode.lib.vcs.backends.base import BaseChangeset
2277 from rhodecode.lib.vcs.backends.base import BaseChangeset
2278 if cs_cache is None:
2278 if cs_cache is None:
2279 # use no-cache version here
2279 # use no-cache version here
2280 scm_repo = self.scm_instance(cache=False, config=config)
2280 scm_repo = self.scm_instance(cache=False, config=config)
2281
2281
2282 empty = not scm_repo or scm_repo.is_empty()
2282 empty = not scm_repo or scm_repo.is_empty()
2283 if not empty:
2283 if not empty:
2284 cs_cache = scm_repo.get_commit(
2284 cs_cache = scm_repo.get_commit(
2285 pre_load=["author", "date", "message", "parents"])
2285 pre_load=["author", "date", "message", "parents"])
2286 else:
2286 else:
2287 cs_cache = EmptyCommit()
2287 cs_cache = EmptyCommit()
2288
2288
2289 if isinstance(cs_cache, BaseChangeset):
2289 if isinstance(cs_cache, BaseChangeset):
2290 cs_cache = cs_cache.__json__()
2290 cs_cache = cs_cache.__json__()
2291
2291
2292 def is_outdated(new_cs_cache):
2292 def is_outdated(new_cs_cache):
2293 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2293 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2294 new_cs_cache['revision'] != self.changeset_cache['revision']):
2294 new_cs_cache['revision'] != self.changeset_cache['revision']):
2295 return True
2295 return True
2296 return False
2296 return False
2297
2297
2298 # check if we have maybe already latest cached revision
2298 # check if we have maybe already latest cached revision
2299 if is_outdated(cs_cache) or not self.changeset_cache:
2299 if is_outdated(cs_cache) or not self.changeset_cache:
2300 _default = datetime.datetime.utcnow()
2300 _default = datetime.datetime.utcnow()
2301 last_change = cs_cache.get('date') or _default
2301 last_change = cs_cache.get('date') or _default
2302 if self.updated_on and self.updated_on > last_change:
2302 if self.updated_on and self.updated_on > last_change:
2303 # we check if last update is newer than the new value
2303 # we check if last update is newer than the new value
2304 # if yes, we use the current timestamp instead. Imagine you get
2304 # if yes, we use the current timestamp instead. Imagine you get
2305 # old commit pushed 1y ago, we'd set last update 1y to ago.
2305 # old commit pushed 1y ago, we'd set last update 1y to ago.
2306 last_change = _default
2306 last_change = _default
2307 log.debug('updated repo %s with new cs cache %s',
2307 log.debug('updated repo %s with new cs cache %s',
2308 self.repo_name, cs_cache)
2308 self.repo_name, cs_cache)
2309 self.updated_on = last_change
2309 self.updated_on = last_change
2310 self.changeset_cache = cs_cache
2310 self.changeset_cache = cs_cache
2311 Session().add(self)
2311 Session().add(self)
2312 Session().commit()
2312 Session().commit()
2313 else:
2313 else:
2314 log.debug('Skipping update_commit_cache for repo:`%s` '
2314 log.debug('Skipping update_commit_cache for repo:`%s` '
2315 'commit already with latest changes', self.repo_name)
2315 'commit already with latest changes', self.repo_name)
2316
2316
2317 @property
2317 @property
2318 def tip(self):
2318 def tip(self):
2319 return self.get_commit('tip')
2319 return self.get_commit('tip')
2320
2320
2321 @property
2321 @property
2322 def author(self):
2322 def author(self):
2323 return self.tip.author
2323 return self.tip.author
2324
2324
2325 @property
2325 @property
2326 def last_change(self):
2326 def last_change(self):
2327 return self.scm_instance().last_change
2327 return self.scm_instance().last_change
2328
2328
2329 def get_comments(self, revisions=None):
2329 def get_comments(self, revisions=None):
2330 """
2330 """
2331 Returns comments for this repository grouped by revisions
2331 Returns comments for this repository grouped by revisions
2332
2332
2333 :param revisions: filter query by revisions only
2333 :param revisions: filter query by revisions only
2334 """
2334 """
2335 cmts = ChangesetComment.query()\
2335 cmts = ChangesetComment.query()\
2336 .filter(ChangesetComment.repo == self)
2336 .filter(ChangesetComment.repo == self)
2337 if revisions:
2337 if revisions:
2338 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2338 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2339 grouped = collections.defaultdict(list)
2339 grouped = collections.defaultdict(list)
2340 for cmt in cmts.all():
2340 for cmt in cmts.all():
2341 grouped[cmt.revision].append(cmt)
2341 grouped[cmt.revision].append(cmt)
2342 return grouped
2342 return grouped
2343
2343
2344 def statuses(self, revisions=None):
2344 def statuses(self, revisions=None):
2345 """
2345 """
2346 Returns statuses for this repository
2346 Returns statuses for this repository
2347
2347
2348 :param revisions: list of revisions to get statuses for
2348 :param revisions: list of revisions to get statuses for
2349 """
2349 """
2350 statuses = ChangesetStatus.query()\
2350 statuses = ChangesetStatus.query()\
2351 .filter(ChangesetStatus.repo == self)\
2351 .filter(ChangesetStatus.repo == self)\
2352 .filter(ChangesetStatus.version == 0)
2352 .filter(ChangesetStatus.version == 0)
2353
2353
2354 if revisions:
2354 if revisions:
2355 # Try doing the filtering in chunks to avoid hitting limits
2355 # Try doing the filtering in chunks to avoid hitting limits
2356 size = 500
2356 size = 500
2357 status_results = []
2357 status_results = []
2358 for chunk in xrange(0, len(revisions), size):
2358 for chunk in xrange(0, len(revisions), size):
2359 status_results += statuses.filter(
2359 status_results += statuses.filter(
2360 ChangesetStatus.revision.in_(
2360 ChangesetStatus.revision.in_(
2361 revisions[chunk: chunk+size])
2361 revisions[chunk: chunk+size])
2362 ).all()
2362 ).all()
2363 else:
2363 else:
2364 status_results = statuses.all()
2364 status_results = statuses.all()
2365
2365
2366 grouped = {}
2366 grouped = {}
2367
2367
2368 # maybe we have open new pullrequest without a status?
2368 # maybe we have open new pullrequest without a status?
2369 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2369 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2370 status_lbl = ChangesetStatus.get_status_lbl(stat)
2370 status_lbl = ChangesetStatus.get_status_lbl(stat)
2371 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2371 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2372 for rev in pr.revisions:
2372 for rev in pr.revisions:
2373 pr_id = pr.pull_request_id
2373 pr_id = pr.pull_request_id
2374 pr_repo = pr.target_repo.repo_name
2374 pr_repo = pr.target_repo.repo_name
2375 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2375 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2376
2376
2377 for stat in status_results:
2377 for stat in status_results:
2378 pr_id = pr_repo = None
2378 pr_id = pr_repo = None
2379 if stat.pull_request:
2379 if stat.pull_request:
2380 pr_id = stat.pull_request.pull_request_id
2380 pr_id = stat.pull_request.pull_request_id
2381 pr_repo = stat.pull_request.target_repo.repo_name
2381 pr_repo = stat.pull_request.target_repo.repo_name
2382 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2382 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2383 pr_id, pr_repo]
2383 pr_id, pr_repo]
2384 return grouped
2384 return grouped
2385
2385
2386 # ==========================================================================
2386 # ==========================================================================
2387 # SCM CACHE INSTANCE
2387 # SCM CACHE INSTANCE
2388 # ==========================================================================
2388 # ==========================================================================
2389
2389
2390 def scm_instance(self, **kwargs):
2390 def scm_instance(self, **kwargs):
2391 import rhodecode
2391 import rhodecode
2392
2392
2393 # Passing a config will not hit the cache currently only used
2393 # Passing a config will not hit the cache currently only used
2394 # for repo2dbmapper
2394 # for repo2dbmapper
2395 config = kwargs.pop('config', None)
2395 config = kwargs.pop('config', None)
2396 cache = kwargs.pop('cache', None)
2396 cache = kwargs.pop('cache', None)
2397 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2397 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2398 # if cache is NOT defined use default global, else we have a full
2398 # if cache is NOT defined use default global, else we have a full
2399 # control over cache behaviour
2399 # control over cache behaviour
2400 if cache is None and full_cache and not config:
2400 if cache is None and full_cache and not config:
2401 return self._get_instance_cached()
2401 return self._get_instance_cached()
2402 return self._get_instance(cache=bool(cache), config=config)
2402 return self._get_instance(cache=bool(cache), config=config)
2403
2403
2404 def _get_instance_cached(self):
2404 def _get_instance_cached(self):
2405 from rhodecode.lib import rc_cache
2405 from rhodecode.lib import rc_cache
2406
2406
2407 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2407 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2408 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2408 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2409 repo_id=self.repo_id)
2409 repo_id=self.repo_id)
2410 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2410 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2411
2411
2412 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2412 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2413 def get_instance_cached(repo_id, context_id):
2413 def get_instance_cached(repo_id, context_id):
2414 return self._get_instance()
2414 return self._get_instance()
2415
2415
2416 # we must use thread scoped cache here,
2416 # we must use thread scoped cache here,
2417 # because each thread of gevent needs it's own not shared connection and cache
2417 # because each thread of gevent needs it's own not shared connection and cache
2418 # we also alter `args` so the cache key is individual for every green thread.
2418 # we also alter `args` so the cache key is individual for every green thread.
2419 inv_context_manager = rc_cache.InvalidationContext(
2419 inv_context_manager = rc_cache.InvalidationContext(
2420 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2420 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2421 thread_scoped=True)
2421 thread_scoped=True)
2422 with inv_context_manager as invalidation_context:
2422 with inv_context_manager as invalidation_context:
2423 args = (self.repo_id, inv_context_manager.cache_key)
2423 args = (self.repo_id, inv_context_manager.cache_key)
2424 # re-compute and store cache if we get invalidate signal
2424 # re-compute and store cache if we get invalidate signal
2425 if invalidation_context.should_invalidate():
2425 if invalidation_context.should_invalidate():
2426 instance = get_instance_cached.refresh(*args)
2426 instance = get_instance_cached.refresh(*args)
2427 else:
2427 else:
2428 instance = get_instance_cached(*args)
2428 instance = get_instance_cached(*args)
2429
2429
2430 log.debug(
2430 log.debug(
2431 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2431 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2432 return instance
2432 return instance
2433
2433
2434 def _get_instance(self, cache=True, config=None):
2434 def _get_instance(self, cache=True, config=None):
2435 config = config or self._config
2435 config = config or self._config
2436 custom_wire = {
2436 custom_wire = {
2437 'cache': cache # controls the vcs.remote cache
2437 'cache': cache # controls the vcs.remote cache
2438 }
2438 }
2439 repo = get_vcs_instance(
2439 repo = get_vcs_instance(
2440 repo_path=safe_str(self.repo_full_path),
2440 repo_path=safe_str(self.repo_full_path),
2441 config=config,
2441 config=config,
2442 with_wire=custom_wire,
2442 with_wire=custom_wire,
2443 create=False,
2443 create=False,
2444 _vcs_alias=self.repo_type)
2444 _vcs_alias=self.repo_type)
2445
2445
2446 return repo
2446 return repo
2447
2447
2448 def __json__(self):
2448 def __json__(self):
2449 return {'landing_rev': self.landing_rev}
2449 return {'landing_rev': self.landing_rev}
2450
2450
2451 def get_dict(self):
2451 def get_dict(self):
2452
2452
2453 # Since we transformed `repo_name` to a hybrid property, we need to
2453 # Since we transformed `repo_name` to a hybrid property, we need to
2454 # keep compatibility with the code which uses `repo_name` field.
2454 # keep compatibility with the code which uses `repo_name` field.
2455
2455
2456 result = super(Repository, self).get_dict()
2456 result = super(Repository, self).get_dict()
2457 result['repo_name'] = result.pop('_repo_name', None)
2457 result['repo_name'] = result.pop('_repo_name', None)
2458 return result
2458 return result
2459
2459
2460
2460
2461 class RepoGroup(Base, BaseModel):
2461 class RepoGroup(Base, BaseModel):
2462 __tablename__ = 'groups'
2462 __tablename__ = 'groups'
2463 __table_args__ = (
2463 __table_args__ = (
2464 UniqueConstraint('group_name', 'group_parent_id'),
2464 UniqueConstraint('group_name', 'group_parent_id'),
2465 base_table_args,
2465 base_table_args,
2466 )
2466 )
2467 __mapper_args__ = {'order_by': 'group_name'}
2467 __mapper_args__ = {'order_by': 'group_name'}
2468
2468
2469 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2469 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2470
2470
2471 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2471 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2472 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2472 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2473 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2473 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2474 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2474 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2475 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2475 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2476 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2476 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2477 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2477 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2478 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2478 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2479 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2479 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2480
2480
2481 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2481 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2482 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2482 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2483 parent_group = relationship('RepoGroup', remote_side=group_id)
2483 parent_group = relationship('RepoGroup', remote_side=group_id)
2484 user = relationship('User')
2484 user = relationship('User')
2485 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2485 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2486
2486
2487 def __init__(self, group_name='', parent_group=None):
2487 def __init__(self, group_name='', parent_group=None):
2488 self.group_name = group_name
2488 self.group_name = group_name
2489 self.parent_group = parent_group
2489 self.parent_group = parent_group
2490
2490
2491 def __unicode__(self):
2491 def __unicode__(self):
2492 return u"<%s('id:%s:%s')>" % (
2492 return u"<%s('id:%s:%s')>" % (
2493 self.__class__.__name__, self.group_id, self.group_name)
2493 self.__class__.__name__, self.group_id, self.group_name)
2494
2494
2495 @validates('group_parent_id')
2495 @validates('group_parent_id')
2496 def validate_group_parent_id(self, key, val):
2496 def validate_group_parent_id(self, key, val):
2497 """
2497 """
2498 Check cycle references for a parent group to self
2498 Check cycle references for a parent group to self
2499 """
2499 """
2500 if self.group_id and val:
2500 if self.group_id and val:
2501 assert val != self.group_id
2501 assert val != self.group_id
2502
2502
2503 return val
2503 return val
2504
2504
2505 @hybrid_property
2505 @hybrid_property
2506 def description_safe(self):
2506 def description_safe(self):
2507 from rhodecode.lib import helpers as h
2507 from rhodecode.lib import helpers as h
2508 return h.escape(self.group_description)
2508 return h.escape(self.group_description)
2509
2509
2510 @classmethod
2510 @classmethod
2511 def _generate_choice(cls, repo_group):
2511 def _generate_choice(cls, repo_group):
2512 from webhelpers.html import literal as _literal
2512 from webhelpers.html import literal as _literal
2513 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2513 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2514 return repo_group.group_id, _name(repo_group.full_path_splitted)
2514 return repo_group.group_id, _name(repo_group.full_path_splitted)
2515
2515
2516 @classmethod
2516 @classmethod
2517 def groups_choices(cls, groups=None, show_empty_group=True):
2517 def groups_choices(cls, groups=None, show_empty_group=True):
2518 if not groups:
2518 if not groups:
2519 groups = cls.query().all()
2519 groups = cls.query().all()
2520
2520
2521 repo_groups = []
2521 repo_groups = []
2522 if show_empty_group:
2522 if show_empty_group:
2523 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2523 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2524
2524
2525 repo_groups.extend([cls._generate_choice(x) for x in groups])
2525 repo_groups.extend([cls._generate_choice(x) for x in groups])
2526
2526
2527 repo_groups = sorted(
2527 repo_groups = sorted(
2528 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2528 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2529 return repo_groups
2529 return repo_groups
2530
2530
2531 @classmethod
2531 @classmethod
2532 def url_sep(cls):
2532 def url_sep(cls):
2533 return URL_SEP
2533 return URL_SEP
2534
2534
2535 @classmethod
2535 @classmethod
2536 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2536 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2537 if case_insensitive:
2537 if case_insensitive:
2538 gr = cls.query().filter(func.lower(cls.group_name)
2538 gr = cls.query().filter(func.lower(cls.group_name)
2539 == func.lower(group_name))
2539 == func.lower(group_name))
2540 else:
2540 else:
2541 gr = cls.query().filter(cls.group_name == group_name)
2541 gr = cls.query().filter(cls.group_name == group_name)
2542 if cache:
2542 if cache:
2543 name_key = _hash_key(group_name)
2543 name_key = _hash_key(group_name)
2544 gr = gr.options(
2544 gr = gr.options(
2545 FromCache("sql_cache_short", "get_group_%s" % name_key))
2545 FromCache("sql_cache_short", "get_group_%s" % name_key))
2546 return gr.scalar()
2546 return gr.scalar()
2547
2547
2548 @classmethod
2548 @classmethod
2549 def get_user_personal_repo_group(cls, user_id):
2549 def get_user_personal_repo_group(cls, user_id):
2550 user = User.get(user_id)
2550 user = User.get(user_id)
2551 if user.username == User.DEFAULT_USER:
2551 if user.username == User.DEFAULT_USER:
2552 return None
2552 return None
2553
2553
2554 return cls.query()\
2554 return cls.query()\
2555 .filter(cls.personal == true()) \
2555 .filter(cls.personal == true()) \
2556 .filter(cls.user == user) \
2556 .filter(cls.user == user) \
2557 .order_by(cls.group_id.asc()) \
2557 .order_by(cls.group_id.asc()) \
2558 .first()
2558 .first()
2559
2559
2560 @classmethod
2560 @classmethod
2561 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2561 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2562 case_insensitive=True):
2562 case_insensitive=True):
2563 q = RepoGroup.query()
2563 q = RepoGroup.query()
2564
2564
2565 if not isinstance(user_id, Optional):
2565 if not isinstance(user_id, Optional):
2566 q = q.filter(RepoGroup.user_id == user_id)
2566 q = q.filter(RepoGroup.user_id == user_id)
2567
2567
2568 if not isinstance(group_id, Optional):
2568 if not isinstance(group_id, Optional):
2569 q = q.filter(RepoGroup.group_parent_id == group_id)
2569 q = q.filter(RepoGroup.group_parent_id == group_id)
2570
2570
2571 if case_insensitive:
2571 if case_insensitive:
2572 q = q.order_by(func.lower(RepoGroup.group_name))
2572 q = q.order_by(func.lower(RepoGroup.group_name))
2573 else:
2573 else:
2574 q = q.order_by(RepoGroup.group_name)
2574 q = q.order_by(RepoGroup.group_name)
2575 return q.all()
2575 return q.all()
2576
2576
2577 @property
2577 @property
2578 def parents(self):
2578 def parents(self):
2579 parents_recursion_limit = 10
2579 parents_recursion_limit = 10
2580 groups = []
2580 groups = []
2581 if self.parent_group is None:
2581 if self.parent_group is None:
2582 return groups
2582 return groups
2583 cur_gr = self.parent_group
2583 cur_gr = self.parent_group
2584 groups.insert(0, cur_gr)
2584 groups.insert(0, cur_gr)
2585 cnt = 0
2585 cnt = 0
2586 while 1:
2586 while 1:
2587 cnt += 1
2587 cnt += 1
2588 gr = getattr(cur_gr, 'parent_group', None)
2588 gr = getattr(cur_gr, 'parent_group', None)
2589 cur_gr = cur_gr.parent_group
2589 cur_gr = cur_gr.parent_group
2590 if gr is None:
2590 if gr is None:
2591 break
2591 break
2592 if cnt == parents_recursion_limit:
2592 if cnt == parents_recursion_limit:
2593 # this will prevent accidental infinit loops
2593 # this will prevent accidental infinit loops
2594 log.error('more than %s parents found for group %s, stopping '
2594 log.error('more than %s parents found for group %s, stopping '
2595 'recursive parent fetching', parents_recursion_limit, self)
2595 'recursive parent fetching', parents_recursion_limit, self)
2596 break
2596 break
2597
2597
2598 groups.insert(0, gr)
2598 groups.insert(0, gr)
2599 return groups
2599 return groups
2600
2600
2601 @property
2601 @property
2602 def last_db_change(self):
2602 def last_db_change(self):
2603 return self.updated_on
2603 return self.updated_on
2604
2604
2605 @property
2605 @property
2606 def children(self):
2606 def children(self):
2607 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2607 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2608
2608
2609 @property
2609 @property
2610 def name(self):
2610 def name(self):
2611 return self.group_name.split(RepoGroup.url_sep())[-1]
2611 return self.group_name.split(RepoGroup.url_sep())[-1]
2612
2612
2613 @property
2613 @property
2614 def full_path(self):
2614 def full_path(self):
2615 return self.group_name
2615 return self.group_name
2616
2616
2617 @property
2617 @property
2618 def full_path_splitted(self):
2618 def full_path_splitted(self):
2619 return self.group_name.split(RepoGroup.url_sep())
2619 return self.group_name.split(RepoGroup.url_sep())
2620
2620
2621 @property
2621 @property
2622 def repositories(self):
2622 def repositories(self):
2623 return Repository.query()\
2623 return Repository.query()\
2624 .filter(Repository.group == self)\
2624 .filter(Repository.group == self)\
2625 .order_by(Repository.repo_name)
2625 .order_by(Repository.repo_name)
2626
2626
2627 @property
2627 @property
2628 def repositories_recursive_count(self):
2628 def repositories_recursive_count(self):
2629 cnt = self.repositories.count()
2629 cnt = self.repositories.count()
2630
2630
2631 def children_count(group):
2631 def children_count(group):
2632 cnt = 0
2632 cnt = 0
2633 for child in group.children:
2633 for child in group.children:
2634 cnt += child.repositories.count()
2634 cnt += child.repositories.count()
2635 cnt += children_count(child)
2635 cnt += children_count(child)
2636 return cnt
2636 return cnt
2637
2637
2638 return cnt + children_count(self)
2638 return cnt + children_count(self)
2639
2639
2640 def _recursive_objects(self, include_repos=True):
2640 def _recursive_objects(self, include_repos=True):
2641 all_ = []
2641 all_ = []
2642
2642
2643 def _get_members(root_gr):
2643 def _get_members(root_gr):
2644 if include_repos:
2644 if include_repos:
2645 for r in root_gr.repositories:
2645 for r in root_gr.repositories:
2646 all_.append(r)
2646 all_.append(r)
2647 childs = root_gr.children.all()
2647 childs = root_gr.children.all()
2648 if childs:
2648 if childs:
2649 for gr in childs:
2649 for gr in childs:
2650 all_.append(gr)
2650 all_.append(gr)
2651 _get_members(gr)
2651 _get_members(gr)
2652
2652
2653 _get_members(self)
2653 _get_members(self)
2654 return [self] + all_
2654 return [self] + all_
2655
2655
2656 def recursive_groups_and_repos(self):
2656 def recursive_groups_and_repos(self):
2657 """
2657 """
2658 Recursive return all groups, with repositories in those groups
2658 Recursive return all groups, with repositories in those groups
2659 """
2659 """
2660 return self._recursive_objects()
2660 return self._recursive_objects()
2661
2661
2662 def recursive_groups(self):
2662 def recursive_groups(self):
2663 """
2663 """
2664 Returns all children groups for this group including children of children
2664 Returns all children groups for this group including children of children
2665 """
2665 """
2666 return self._recursive_objects(include_repos=False)
2666 return self._recursive_objects(include_repos=False)
2667
2667
2668 def get_new_name(self, group_name):
2668 def get_new_name(self, group_name):
2669 """
2669 """
2670 returns new full group name based on parent and new name
2670 returns new full group name based on parent and new name
2671
2671
2672 :param group_name:
2672 :param group_name:
2673 """
2673 """
2674 path_prefix = (self.parent_group.full_path_splitted if
2674 path_prefix = (self.parent_group.full_path_splitted if
2675 self.parent_group else [])
2675 self.parent_group else [])
2676 return RepoGroup.url_sep().join(path_prefix + [group_name])
2676 return RepoGroup.url_sep().join(path_prefix + [group_name])
2677
2677
2678 def permissions(self, with_admins=True, with_owner=True,
2678 def permissions(self, with_admins=True, with_owner=True,
2679 expand_from_user_groups=False):
2679 expand_from_user_groups=False):
2680 """
2680 """
2681 Permissions for repository groups
2681 Permissions for repository groups
2682 """
2682 """
2683 _admin_perm = 'group.admin'
2683 _admin_perm = 'group.admin'
2684
2684
2685 owner_row = []
2685 owner_row = []
2686 if with_owner:
2686 if with_owner:
2687 usr = AttributeDict(self.user.get_dict())
2687 usr = AttributeDict(self.user.get_dict())
2688 usr.owner_row = True
2688 usr.owner_row = True
2689 usr.permission = _admin_perm
2689 usr.permission = _admin_perm
2690 owner_row.append(usr)
2690 owner_row.append(usr)
2691
2691
2692 super_admin_ids = []
2692 super_admin_ids = []
2693 super_admin_rows = []
2693 super_admin_rows = []
2694 if with_admins:
2694 if with_admins:
2695 for usr in User.get_all_super_admins():
2695 for usr in User.get_all_super_admins():
2696 super_admin_ids.append(usr.user_id)
2696 super_admin_ids.append(usr.user_id)
2697 # if this admin is also owner, don't double the record
2697 # if this admin is also owner, don't double the record
2698 if usr.user_id == owner_row[0].user_id:
2698 if usr.user_id == owner_row[0].user_id:
2699 owner_row[0].admin_row = True
2699 owner_row[0].admin_row = True
2700 else:
2700 else:
2701 usr = AttributeDict(usr.get_dict())
2701 usr = AttributeDict(usr.get_dict())
2702 usr.admin_row = True
2702 usr.admin_row = True
2703 usr.permission = _admin_perm
2703 usr.permission = _admin_perm
2704 super_admin_rows.append(usr)
2704 super_admin_rows.append(usr)
2705
2705
2706 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2706 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2707 q = q.options(joinedload(UserRepoGroupToPerm.group),
2707 q = q.options(joinedload(UserRepoGroupToPerm.group),
2708 joinedload(UserRepoGroupToPerm.user),
2708 joinedload(UserRepoGroupToPerm.user),
2709 joinedload(UserRepoGroupToPerm.permission),)
2709 joinedload(UserRepoGroupToPerm.permission),)
2710
2710
2711 # get owners and admins and permissions. We do a trick of re-writing
2711 # get owners and admins and permissions. We do a trick of re-writing
2712 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2712 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2713 # has a global reference and changing one object propagates to all
2713 # has a global reference and changing one object propagates to all
2714 # others. This means if admin is also an owner admin_row that change
2714 # others. This means if admin is also an owner admin_row that change
2715 # would propagate to both objects
2715 # would propagate to both objects
2716 perm_rows = []
2716 perm_rows = []
2717 for _usr in q.all():
2717 for _usr in q.all():
2718 usr = AttributeDict(_usr.user.get_dict())
2718 usr = AttributeDict(_usr.user.get_dict())
2719 # if this user is also owner/admin, mark as duplicate record
2719 # if this user is also owner/admin, mark as duplicate record
2720 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2720 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2721 usr.duplicate_perm = True
2721 usr.duplicate_perm = True
2722 usr.permission = _usr.permission.permission_name
2722 usr.permission = _usr.permission.permission_name
2723 perm_rows.append(usr)
2723 perm_rows.append(usr)
2724
2724
2725 # filter the perm rows by 'default' first and then sort them by
2725 # filter the perm rows by 'default' first and then sort them by
2726 # admin,write,read,none permissions sorted again alphabetically in
2726 # admin,write,read,none permissions sorted again alphabetically in
2727 # each group
2727 # each group
2728 perm_rows = sorted(perm_rows, key=display_user_sort)
2728 perm_rows = sorted(perm_rows, key=display_user_sort)
2729
2729
2730 user_groups_rows = []
2730 user_groups_rows = []
2731 if expand_from_user_groups:
2731 if expand_from_user_groups:
2732 for ug in self.permission_user_groups(with_members=True):
2732 for ug in self.permission_user_groups(with_members=True):
2733 for user_data in ug.members:
2733 for user_data in ug.members:
2734 user_groups_rows.append(user_data)
2734 user_groups_rows.append(user_data)
2735
2735
2736 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2736 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2737
2737
2738 def permission_user_groups(self, with_members=False):
2738 def permission_user_groups(self, with_members=False):
2739 q = UserGroupRepoGroupToPerm.query()\
2739 q = UserGroupRepoGroupToPerm.query()\
2740 .filter(UserGroupRepoGroupToPerm.group == self)
2740 .filter(UserGroupRepoGroupToPerm.group == self)
2741 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2741 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2742 joinedload(UserGroupRepoGroupToPerm.users_group),
2742 joinedload(UserGroupRepoGroupToPerm.users_group),
2743 joinedload(UserGroupRepoGroupToPerm.permission),)
2743 joinedload(UserGroupRepoGroupToPerm.permission),)
2744
2744
2745 perm_rows = []
2745 perm_rows = []
2746 for _user_group in q.all():
2746 for _user_group in q.all():
2747 entry = AttributeDict(_user_group.users_group.get_dict())
2747 entry = AttributeDict(_user_group.users_group.get_dict())
2748 entry.permission = _user_group.permission.permission_name
2748 entry.permission = _user_group.permission.permission_name
2749 if with_members:
2749 if with_members:
2750 entry.members = [x.user.get_dict()
2750 entry.members = [x.user.get_dict()
2751 for x in _user_group.users_group.members]
2751 for x in _user_group.users_group.members]
2752 perm_rows.append(entry)
2752 perm_rows.append(entry)
2753
2753
2754 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2754 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2755 return perm_rows
2755 return perm_rows
2756
2756
2757 def get_api_data(self):
2757 def get_api_data(self):
2758 """
2758 """
2759 Common function for generating api data
2759 Common function for generating api data
2760
2760
2761 """
2761 """
2762 group = self
2762 group = self
2763 data = {
2763 data = {
2764 'group_id': group.group_id,
2764 'group_id': group.group_id,
2765 'group_name': group.group_name,
2765 'group_name': group.group_name,
2766 'group_description': group.description_safe,
2766 'group_description': group.description_safe,
2767 'parent_group': group.parent_group.group_name if group.parent_group else None,
2767 'parent_group': group.parent_group.group_name if group.parent_group else None,
2768 'repositories': [x.repo_name for x in group.repositories],
2768 'repositories': [x.repo_name for x in group.repositories],
2769 'owner': group.user.username,
2769 'owner': group.user.username,
2770 }
2770 }
2771 return data
2771 return data
2772
2772
2773
2773
2774 class Permission(Base, BaseModel):
2774 class Permission(Base, BaseModel):
2775 __tablename__ = 'permissions'
2775 __tablename__ = 'permissions'
2776 __table_args__ = (
2776 __table_args__ = (
2777 Index('p_perm_name_idx', 'permission_name'),
2777 Index('p_perm_name_idx', 'permission_name'),
2778 base_table_args,
2778 base_table_args,
2779 )
2779 )
2780
2780
2781 PERMS = [
2781 PERMS = [
2782 ('hg.admin', _('RhodeCode Super Administrator')),
2782 ('hg.admin', _('RhodeCode Super Administrator')),
2783
2783
2784 ('repository.none', _('Repository no access')),
2784 ('repository.none', _('Repository no access')),
2785 ('repository.read', _('Repository read access')),
2785 ('repository.read', _('Repository read access')),
2786 ('repository.write', _('Repository write access')),
2786 ('repository.write', _('Repository write access')),
2787 ('repository.admin', _('Repository admin access')),
2787 ('repository.admin', _('Repository admin access')),
2788
2788
2789 ('group.none', _('Repository group no access')),
2789 ('group.none', _('Repository group no access')),
2790 ('group.read', _('Repository group read access')),
2790 ('group.read', _('Repository group read access')),
2791 ('group.write', _('Repository group write access')),
2791 ('group.write', _('Repository group write access')),
2792 ('group.admin', _('Repository group admin access')),
2792 ('group.admin', _('Repository group admin access')),
2793
2793
2794 ('usergroup.none', _('User group no access')),
2794 ('usergroup.none', _('User group no access')),
2795 ('usergroup.read', _('User group read access')),
2795 ('usergroup.read', _('User group read access')),
2796 ('usergroup.write', _('User group write access')),
2796 ('usergroup.write', _('User group write access')),
2797 ('usergroup.admin', _('User group admin access')),
2797 ('usergroup.admin', _('User group admin access')),
2798
2798
2799 ('branch.none', _('Branch no permissions')),
2799 ('branch.none', _('Branch no permissions')),
2800 ('branch.merge', _('Branch access by web merge')),
2800 ('branch.merge', _('Branch access by web merge')),
2801 ('branch.push', _('Branch access by push')),
2801 ('branch.push', _('Branch access by push')),
2802 ('branch.push_force', _('Branch access by push with force')),
2802 ('branch.push_force', _('Branch access by push with force')),
2803
2803
2804 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2804 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2805 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2805 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2806
2806
2807 ('hg.usergroup.create.false', _('User Group creation disabled')),
2807 ('hg.usergroup.create.false', _('User Group creation disabled')),
2808 ('hg.usergroup.create.true', _('User Group creation enabled')),
2808 ('hg.usergroup.create.true', _('User Group creation enabled')),
2809
2809
2810 ('hg.create.none', _('Repository creation disabled')),
2810 ('hg.create.none', _('Repository creation disabled')),
2811 ('hg.create.repository', _('Repository creation enabled')),
2811 ('hg.create.repository', _('Repository creation enabled')),
2812 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2812 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2813 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2813 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2814
2814
2815 ('hg.fork.none', _('Repository forking disabled')),
2815 ('hg.fork.none', _('Repository forking disabled')),
2816 ('hg.fork.repository', _('Repository forking enabled')),
2816 ('hg.fork.repository', _('Repository forking enabled')),
2817
2817
2818 ('hg.register.none', _('Registration disabled')),
2818 ('hg.register.none', _('Registration disabled')),
2819 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2819 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2820 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2820 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2821
2821
2822 ('hg.password_reset.enabled', _('Password reset enabled')),
2822 ('hg.password_reset.enabled', _('Password reset enabled')),
2823 ('hg.password_reset.hidden', _('Password reset hidden')),
2823 ('hg.password_reset.hidden', _('Password reset hidden')),
2824 ('hg.password_reset.disabled', _('Password reset disabled')),
2824 ('hg.password_reset.disabled', _('Password reset disabled')),
2825
2825
2826 ('hg.extern_activate.manual', _('Manual activation of external account')),
2826 ('hg.extern_activate.manual', _('Manual activation of external account')),
2827 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2827 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2828
2828
2829 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2829 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2830 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2830 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2831 ]
2831 ]
2832
2832
2833 # definition of system default permissions for DEFAULT user, created on
2833 # definition of system default permissions for DEFAULT user, created on
2834 # system setup
2834 # system setup
2835 DEFAULT_USER_PERMISSIONS = [
2835 DEFAULT_USER_PERMISSIONS = [
2836 # object perms
2836 # object perms
2837 'repository.read',
2837 'repository.read',
2838 'group.read',
2838 'group.read',
2839 'usergroup.read',
2839 'usergroup.read',
2840 # branch, for backward compat we need same value as before so forced pushed
2840 # branch, for backward compat we need same value as before so forced pushed
2841 'branch.push_force',
2841 'branch.push_force',
2842 # global
2842 # global
2843 'hg.create.repository',
2843 'hg.create.repository',
2844 'hg.repogroup.create.false',
2844 'hg.repogroup.create.false',
2845 'hg.usergroup.create.false',
2845 'hg.usergroup.create.false',
2846 'hg.create.write_on_repogroup.true',
2846 'hg.create.write_on_repogroup.true',
2847 'hg.fork.repository',
2847 'hg.fork.repository',
2848 'hg.register.manual_activate',
2848 'hg.register.manual_activate',
2849 'hg.password_reset.enabled',
2849 'hg.password_reset.enabled',
2850 'hg.extern_activate.auto',
2850 'hg.extern_activate.auto',
2851 'hg.inherit_default_perms.true',
2851 'hg.inherit_default_perms.true',
2852 ]
2852 ]
2853
2853
2854 # defines which permissions are more important higher the more important
2854 # defines which permissions are more important higher the more important
2855 # Weight defines which permissions are more important.
2855 # Weight defines which permissions are more important.
2856 # The higher number the more important.
2856 # The higher number the more important.
2857 PERM_WEIGHTS = {
2857 PERM_WEIGHTS = {
2858 'repository.none': 0,
2858 'repository.none': 0,
2859 'repository.read': 1,
2859 'repository.read': 1,
2860 'repository.write': 3,
2860 'repository.write': 3,
2861 'repository.admin': 4,
2861 'repository.admin': 4,
2862
2862
2863 'group.none': 0,
2863 'group.none': 0,
2864 'group.read': 1,
2864 'group.read': 1,
2865 'group.write': 3,
2865 'group.write': 3,
2866 'group.admin': 4,
2866 'group.admin': 4,
2867
2867
2868 'usergroup.none': 0,
2868 'usergroup.none': 0,
2869 'usergroup.read': 1,
2869 'usergroup.read': 1,
2870 'usergroup.write': 3,
2870 'usergroup.write': 3,
2871 'usergroup.admin': 4,
2871 'usergroup.admin': 4,
2872
2872
2873 'branch.none': 0,
2873 'branch.none': 0,
2874 'branch.merge': 1,
2874 'branch.merge': 1,
2875 'branch.push': 3,
2875 'branch.push': 3,
2876 'branch.push_force': 4,
2876 'branch.push_force': 4,
2877
2877
2878 'hg.repogroup.create.false': 0,
2878 'hg.repogroup.create.false': 0,
2879 'hg.repogroup.create.true': 1,
2879 'hg.repogroup.create.true': 1,
2880
2880
2881 'hg.usergroup.create.false': 0,
2881 'hg.usergroup.create.false': 0,
2882 'hg.usergroup.create.true': 1,
2882 'hg.usergroup.create.true': 1,
2883
2883
2884 'hg.fork.none': 0,
2884 'hg.fork.none': 0,
2885 'hg.fork.repository': 1,
2885 'hg.fork.repository': 1,
2886 'hg.create.none': 0,
2886 'hg.create.none': 0,
2887 'hg.create.repository': 1
2887 'hg.create.repository': 1
2888 }
2888 }
2889
2889
2890 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2890 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2891 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2891 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2892 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2892 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2893
2893
2894 def __unicode__(self):
2894 def __unicode__(self):
2895 return u"<%s('%s:%s')>" % (
2895 return u"<%s('%s:%s')>" % (
2896 self.__class__.__name__, self.permission_id, self.permission_name
2896 self.__class__.__name__, self.permission_id, self.permission_name
2897 )
2897 )
2898
2898
2899 @classmethod
2899 @classmethod
2900 def get_by_key(cls, key):
2900 def get_by_key(cls, key):
2901 return cls.query().filter(cls.permission_name == key).scalar()
2901 return cls.query().filter(cls.permission_name == key).scalar()
2902
2902
2903 @classmethod
2903 @classmethod
2904 def get_default_repo_perms(cls, user_id, repo_id=None):
2904 def get_default_repo_perms(cls, user_id, repo_id=None):
2905 q = Session().query(UserRepoToPerm, Repository, Permission)\
2905 q = Session().query(UserRepoToPerm, Repository, Permission)\
2906 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2906 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2907 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2907 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2908 .filter(UserRepoToPerm.user_id == user_id)
2908 .filter(UserRepoToPerm.user_id == user_id)
2909 if repo_id:
2909 if repo_id:
2910 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2910 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2911 return q.all()
2911 return q.all()
2912
2912
2913 @classmethod
2913 @classmethod
2914 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2914 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2915 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2915 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2916 .join(
2916 .join(
2917 Permission,
2917 Permission,
2918 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2918 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2919 .join(
2919 .join(
2920 UserRepoToPerm,
2920 UserRepoToPerm,
2921 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2921 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2922 .filter(UserRepoToPerm.user_id == user_id)
2922 .filter(UserRepoToPerm.user_id == user_id)
2923
2923
2924 if repo_id:
2924 if repo_id:
2925 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2925 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2926 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2926 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2927
2927
2928 @classmethod
2928 @classmethod
2929 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2929 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2930 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2930 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2931 .join(
2931 .join(
2932 Permission,
2932 Permission,
2933 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2933 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2934 .join(
2934 .join(
2935 Repository,
2935 Repository,
2936 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2936 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2937 .join(
2937 .join(
2938 UserGroup,
2938 UserGroup,
2939 UserGroupRepoToPerm.users_group_id ==
2939 UserGroupRepoToPerm.users_group_id ==
2940 UserGroup.users_group_id)\
2940 UserGroup.users_group_id)\
2941 .join(
2941 .join(
2942 UserGroupMember,
2942 UserGroupMember,
2943 UserGroupRepoToPerm.users_group_id ==
2943 UserGroupRepoToPerm.users_group_id ==
2944 UserGroupMember.users_group_id)\
2944 UserGroupMember.users_group_id)\
2945 .filter(
2945 .filter(
2946 UserGroupMember.user_id == user_id,
2946 UserGroupMember.user_id == user_id,
2947 UserGroup.users_group_active == true())
2947 UserGroup.users_group_active == true())
2948 if repo_id:
2948 if repo_id:
2949 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2949 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2950 return q.all()
2950 return q.all()
2951
2951
2952 @classmethod
2952 @classmethod
2953 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2953 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2954 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2954 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2955 .join(
2955 .join(
2956 Permission,
2956 Permission,
2957 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2957 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2958 .join(
2958 .join(
2959 UserGroupRepoToPerm,
2959 UserGroupRepoToPerm,
2960 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2960 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2961 .join(
2961 .join(
2962 UserGroup,
2962 UserGroup,
2963 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2963 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2964 .join(
2964 .join(
2965 UserGroupMember,
2965 UserGroupMember,
2966 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2966 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2967 .filter(
2967 .filter(
2968 UserGroupMember.user_id == user_id,
2968 UserGroupMember.user_id == user_id,
2969 UserGroup.users_group_active == true())
2969 UserGroup.users_group_active == true())
2970
2970
2971 if repo_id:
2971 if repo_id:
2972 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
2972 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
2973 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
2973 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
2974
2974
2975 @classmethod
2975 @classmethod
2976 def get_default_group_perms(cls, user_id, repo_group_id=None):
2976 def get_default_group_perms(cls, user_id, repo_group_id=None):
2977 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2977 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2978 .join(
2978 .join(
2979 Permission,
2979 Permission,
2980 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
2980 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
2981 .join(
2981 .join(
2982 RepoGroup,
2982 RepoGroup,
2983 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
2983 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
2984 .filter(UserRepoGroupToPerm.user_id == user_id)
2984 .filter(UserRepoGroupToPerm.user_id == user_id)
2985 if repo_group_id:
2985 if repo_group_id:
2986 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2986 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2987 return q.all()
2987 return q.all()
2988
2988
2989 @classmethod
2989 @classmethod
2990 def get_default_group_perms_from_user_group(
2990 def get_default_group_perms_from_user_group(
2991 cls, user_id, repo_group_id=None):
2991 cls, user_id, repo_group_id=None):
2992 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2992 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2993 .join(
2993 .join(
2994 Permission,
2994 Permission,
2995 UserGroupRepoGroupToPerm.permission_id ==
2995 UserGroupRepoGroupToPerm.permission_id ==
2996 Permission.permission_id)\
2996 Permission.permission_id)\
2997 .join(
2997 .join(
2998 RepoGroup,
2998 RepoGroup,
2999 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2999 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3000 .join(
3000 .join(
3001 UserGroup,
3001 UserGroup,
3002 UserGroupRepoGroupToPerm.users_group_id ==
3002 UserGroupRepoGroupToPerm.users_group_id ==
3003 UserGroup.users_group_id)\
3003 UserGroup.users_group_id)\
3004 .join(
3004 .join(
3005 UserGroupMember,
3005 UserGroupMember,
3006 UserGroupRepoGroupToPerm.users_group_id ==
3006 UserGroupRepoGroupToPerm.users_group_id ==
3007 UserGroupMember.users_group_id)\
3007 UserGroupMember.users_group_id)\
3008 .filter(
3008 .filter(
3009 UserGroupMember.user_id == user_id,
3009 UserGroupMember.user_id == user_id,
3010 UserGroup.users_group_active == true())
3010 UserGroup.users_group_active == true())
3011 if repo_group_id:
3011 if repo_group_id:
3012 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3012 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3013 return q.all()
3013 return q.all()
3014
3014
3015 @classmethod
3015 @classmethod
3016 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3016 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3017 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3017 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3018 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3018 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3019 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3019 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3020 .filter(UserUserGroupToPerm.user_id == user_id)
3020 .filter(UserUserGroupToPerm.user_id == user_id)
3021 if user_group_id:
3021 if user_group_id:
3022 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3022 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3023 return q.all()
3023 return q.all()
3024
3024
3025 @classmethod
3025 @classmethod
3026 def get_default_user_group_perms_from_user_group(
3026 def get_default_user_group_perms_from_user_group(
3027 cls, user_id, user_group_id=None):
3027 cls, user_id, user_group_id=None):
3028 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3028 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3029 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3029 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3030 .join(
3030 .join(
3031 Permission,
3031 Permission,
3032 UserGroupUserGroupToPerm.permission_id ==
3032 UserGroupUserGroupToPerm.permission_id ==
3033 Permission.permission_id)\
3033 Permission.permission_id)\
3034 .join(
3034 .join(
3035 TargetUserGroup,
3035 TargetUserGroup,
3036 UserGroupUserGroupToPerm.target_user_group_id ==
3036 UserGroupUserGroupToPerm.target_user_group_id ==
3037 TargetUserGroup.users_group_id)\
3037 TargetUserGroup.users_group_id)\
3038 .join(
3038 .join(
3039 UserGroup,
3039 UserGroup,
3040 UserGroupUserGroupToPerm.user_group_id ==
3040 UserGroupUserGroupToPerm.user_group_id ==
3041 UserGroup.users_group_id)\
3041 UserGroup.users_group_id)\
3042 .join(
3042 .join(
3043 UserGroupMember,
3043 UserGroupMember,
3044 UserGroupUserGroupToPerm.user_group_id ==
3044 UserGroupUserGroupToPerm.user_group_id ==
3045 UserGroupMember.users_group_id)\
3045 UserGroupMember.users_group_id)\
3046 .filter(
3046 .filter(
3047 UserGroupMember.user_id == user_id,
3047 UserGroupMember.user_id == user_id,
3048 UserGroup.users_group_active == true())
3048 UserGroup.users_group_active == true())
3049 if user_group_id:
3049 if user_group_id:
3050 q = q.filter(
3050 q = q.filter(
3051 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3051 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3052
3052
3053 return q.all()
3053 return q.all()
3054
3054
3055
3055
3056 class UserRepoToPerm(Base, BaseModel):
3056 class UserRepoToPerm(Base, BaseModel):
3057 __tablename__ = 'repo_to_perm'
3057 __tablename__ = 'repo_to_perm'
3058 __table_args__ = (
3058 __table_args__ = (
3059 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3059 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3060 base_table_args
3060 base_table_args
3061 )
3061 )
3062
3062
3063 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3063 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3065 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3065 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3066 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3066 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3067
3067
3068 user = relationship('User')
3068 user = relationship('User')
3069 repository = relationship('Repository')
3069 repository = relationship('Repository')
3070 permission = relationship('Permission')
3070 permission = relationship('Permission')
3071
3071
3072 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3072 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3073
3073
3074 @classmethod
3074 @classmethod
3075 def create(cls, user, repository, permission):
3075 def create(cls, user, repository, permission):
3076 n = cls()
3076 n = cls()
3077 n.user = user
3077 n.user = user
3078 n.repository = repository
3078 n.repository = repository
3079 n.permission = permission
3079 n.permission = permission
3080 Session().add(n)
3080 Session().add(n)
3081 return n
3081 return n
3082
3082
3083 def __unicode__(self):
3083 def __unicode__(self):
3084 return u'<%s => %s >' % (self.user, self.repository)
3084 return u'<%s => %s >' % (self.user, self.repository)
3085
3085
3086
3086
3087 class UserUserGroupToPerm(Base, BaseModel):
3087 class UserUserGroupToPerm(Base, BaseModel):
3088 __tablename__ = 'user_user_group_to_perm'
3088 __tablename__ = 'user_user_group_to_perm'
3089 __table_args__ = (
3089 __table_args__ = (
3090 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3090 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3091 base_table_args
3091 base_table_args
3092 )
3092 )
3093
3093
3094 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3094 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3096 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3096 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3097 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3097 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3098
3098
3099 user = relationship('User')
3099 user = relationship('User')
3100 user_group = relationship('UserGroup')
3100 user_group = relationship('UserGroup')
3101 permission = relationship('Permission')
3101 permission = relationship('Permission')
3102
3102
3103 @classmethod
3103 @classmethod
3104 def create(cls, user, user_group, permission):
3104 def create(cls, user, user_group, permission):
3105 n = cls()
3105 n = cls()
3106 n.user = user
3106 n.user = user
3107 n.user_group = user_group
3107 n.user_group = user_group
3108 n.permission = permission
3108 n.permission = permission
3109 Session().add(n)
3109 Session().add(n)
3110 return n
3110 return n
3111
3111
3112 def __unicode__(self):
3112 def __unicode__(self):
3113 return u'<%s => %s >' % (self.user, self.user_group)
3113 return u'<%s => %s >' % (self.user, self.user_group)
3114
3114
3115
3115
3116 class UserToPerm(Base, BaseModel):
3116 class UserToPerm(Base, BaseModel):
3117 __tablename__ = 'user_to_perm'
3117 __tablename__ = 'user_to_perm'
3118 __table_args__ = (
3118 __table_args__ = (
3119 UniqueConstraint('user_id', 'permission_id'),
3119 UniqueConstraint('user_id', 'permission_id'),
3120 base_table_args
3120 base_table_args
3121 )
3121 )
3122
3122
3123 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3123 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3126
3126
3127 user = relationship('User')
3127 user = relationship('User')
3128 permission = relationship('Permission', lazy='joined')
3128 permission = relationship('Permission', lazy='joined')
3129
3129
3130 def __unicode__(self):
3130 def __unicode__(self):
3131 return u'<%s => %s >' % (self.user, self.permission)
3131 return u'<%s => %s >' % (self.user, self.permission)
3132
3132
3133
3133
3134 class UserGroupRepoToPerm(Base, BaseModel):
3134 class UserGroupRepoToPerm(Base, BaseModel):
3135 __tablename__ = 'users_group_repo_to_perm'
3135 __tablename__ = 'users_group_repo_to_perm'
3136 __table_args__ = (
3136 __table_args__ = (
3137 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3137 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3138 base_table_args
3138 base_table_args
3139 )
3139 )
3140
3140
3141 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3141 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3142 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3142 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3143 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3143 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3144 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3144 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3145
3145
3146 users_group = relationship('UserGroup')
3146 users_group = relationship('UserGroup')
3147 permission = relationship('Permission')
3147 permission = relationship('Permission')
3148 repository = relationship('Repository')
3148 repository = relationship('Repository')
3149 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3149 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3150
3150
3151 @classmethod
3151 @classmethod
3152 def create(cls, users_group, repository, permission):
3152 def create(cls, users_group, repository, permission):
3153 n = cls()
3153 n = cls()
3154 n.users_group = users_group
3154 n.users_group = users_group
3155 n.repository = repository
3155 n.repository = repository
3156 n.permission = permission
3156 n.permission = permission
3157 Session().add(n)
3157 Session().add(n)
3158 return n
3158 return n
3159
3159
3160 def __unicode__(self):
3160 def __unicode__(self):
3161 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3161 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3162
3162
3163
3163
3164 class UserGroupUserGroupToPerm(Base, BaseModel):
3164 class UserGroupUserGroupToPerm(Base, BaseModel):
3165 __tablename__ = 'user_group_user_group_to_perm'
3165 __tablename__ = 'user_group_user_group_to_perm'
3166 __table_args__ = (
3166 __table_args__ = (
3167 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3167 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3168 CheckConstraint('target_user_group_id != user_group_id'),
3168 CheckConstraint('target_user_group_id != user_group_id'),
3169 base_table_args
3169 base_table_args
3170 )
3170 )
3171
3171
3172 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)
3172 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)
3173 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3173 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3174 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3174 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3175 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3175 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3176
3176
3177 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3177 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3178 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3178 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3179 permission = relationship('Permission')
3179 permission = relationship('Permission')
3180
3180
3181 @classmethod
3181 @classmethod
3182 def create(cls, target_user_group, user_group, permission):
3182 def create(cls, target_user_group, user_group, permission):
3183 n = cls()
3183 n = cls()
3184 n.target_user_group = target_user_group
3184 n.target_user_group = target_user_group
3185 n.user_group = user_group
3185 n.user_group = user_group
3186 n.permission = permission
3186 n.permission = permission
3187 Session().add(n)
3187 Session().add(n)
3188 return n
3188 return n
3189
3189
3190 def __unicode__(self):
3190 def __unicode__(self):
3191 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3191 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3192
3192
3193
3193
3194 class UserGroupToPerm(Base, BaseModel):
3194 class UserGroupToPerm(Base, BaseModel):
3195 __tablename__ = 'users_group_to_perm'
3195 __tablename__ = 'users_group_to_perm'
3196 __table_args__ = (
3196 __table_args__ = (
3197 UniqueConstraint('users_group_id', 'permission_id',),
3197 UniqueConstraint('users_group_id', 'permission_id',),
3198 base_table_args
3198 base_table_args
3199 )
3199 )
3200
3200
3201 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3201 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3202 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3202 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3203 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3203 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3204
3204
3205 users_group = relationship('UserGroup')
3205 users_group = relationship('UserGroup')
3206 permission = relationship('Permission')
3206 permission = relationship('Permission')
3207
3207
3208
3208
3209 class UserRepoGroupToPerm(Base, BaseModel):
3209 class UserRepoGroupToPerm(Base, BaseModel):
3210 __tablename__ = 'user_repo_group_to_perm'
3210 __tablename__ = 'user_repo_group_to_perm'
3211 __table_args__ = (
3211 __table_args__ = (
3212 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3212 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3213 base_table_args
3213 base_table_args
3214 )
3214 )
3215
3215
3216 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3216 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3218 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3218 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3219 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3219 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3220
3220
3221 user = relationship('User')
3221 user = relationship('User')
3222 group = relationship('RepoGroup')
3222 group = relationship('RepoGroup')
3223 permission = relationship('Permission')
3223 permission = relationship('Permission')
3224
3224
3225 @classmethod
3225 @classmethod
3226 def create(cls, user, repository_group, permission):
3226 def create(cls, user, repository_group, permission):
3227 n = cls()
3227 n = cls()
3228 n.user = user
3228 n.user = user
3229 n.group = repository_group
3229 n.group = repository_group
3230 n.permission = permission
3230 n.permission = permission
3231 Session().add(n)
3231 Session().add(n)
3232 return n
3232 return n
3233
3233
3234
3234
3235 class UserGroupRepoGroupToPerm(Base, BaseModel):
3235 class UserGroupRepoGroupToPerm(Base, BaseModel):
3236 __tablename__ = 'users_group_repo_group_to_perm'
3236 __tablename__ = 'users_group_repo_group_to_perm'
3237 __table_args__ = (
3237 __table_args__ = (
3238 UniqueConstraint('users_group_id', 'group_id'),
3238 UniqueConstraint('users_group_id', 'group_id'),
3239 base_table_args
3239 base_table_args
3240 )
3240 )
3241
3241
3242 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)
3242 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)
3243 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3243 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3244 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3244 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3245 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3245 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3246
3246
3247 users_group = relationship('UserGroup')
3247 users_group = relationship('UserGroup')
3248 permission = relationship('Permission')
3248 permission = relationship('Permission')
3249 group = relationship('RepoGroup')
3249 group = relationship('RepoGroup')
3250
3250
3251 @classmethod
3251 @classmethod
3252 def create(cls, user_group, repository_group, permission):
3252 def create(cls, user_group, repository_group, permission):
3253 n = cls()
3253 n = cls()
3254 n.users_group = user_group
3254 n.users_group = user_group
3255 n.group = repository_group
3255 n.group = repository_group
3256 n.permission = permission
3256 n.permission = permission
3257 Session().add(n)
3257 Session().add(n)
3258 return n
3258 return n
3259
3259
3260 def __unicode__(self):
3260 def __unicode__(self):
3261 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3261 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3262
3262
3263
3263
3264 class Statistics(Base, BaseModel):
3264 class Statistics(Base, BaseModel):
3265 __tablename__ = 'statistics'
3265 __tablename__ = 'statistics'
3266 __table_args__ = (
3266 __table_args__ = (
3267 base_table_args
3267 base_table_args
3268 )
3268 )
3269
3269
3270 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3270 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3271 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3271 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3272 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3272 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3273 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3273 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3274 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3274 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3275 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3275 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3276
3276
3277 repository = relationship('Repository', single_parent=True)
3277 repository = relationship('Repository', single_parent=True)
3278
3278
3279
3279
3280 class UserFollowing(Base, BaseModel):
3280 class UserFollowing(Base, BaseModel):
3281 __tablename__ = 'user_followings'
3281 __tablename__ = 'user_followings'
3282 __table_args__ = (
3282 __table_args__ = (
3283 UniqueConstraint('user_id', 'follows_repository_id'),
3283 UniqueConstraint('user_id', 'follows_repository_id'),
3284 UniqueConstraint('user_id', 'follows_user_id'),
3284 UniqueConstraint('user_id', 'follows_user_id'),
3285 base_table_args
3285 base_table_args
3286 )
3286 )
3287
3287
3288 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3288 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3289 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3289 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3290 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3290 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3291 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3291 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3292 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3292 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3293
3293
3294 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3294 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3295
3295
3296 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3296 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3297 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3297 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3298
3298
3299 @classmethod
3299 @classmethod
3300 def get_repo_followers(cls, repo_id):
3300 def get_repo_followers(cls, repo_id):
3301 return cls.query().filter(cls.follows_repo_id == repo_id)
3301 return cls.query().filter(cls.follows_repo_id == repo_id)
3302
3302
3303
3303
3304 class CacheKey(Base, BaseModel):
3304 class CacheKey(Base, BaseModel):
3305 __tablename__ = 'cache_invalidation'
3305 __tablename__ = 'cache_invalidation'
3306 __table_args__ = (
3306 __table_args__ = (
3307 UniqueConstraint('cache_key'),
3307 UniqueConstraint('cache_key'),
3308 Index('key_idx', 'cache_key'),
3308 Index('key_idx', 'cache_key'),
3309 base_table_args,
3309 base_table_args,
3310 )
3310 )
3311
3311
3312 CACHE_TYPE_FEED = 'FEED'
3312 CACHE_TYPE_FEED = 'FEED'
3313 CACHE_TYPE_README = 'README'
3313 CACHE_TYPE_README = 'README'
3314 # namespaces used to register process/thread aware caches
3314 # namespaces used to register process/thread aware caches
3315 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3315 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3316 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3316 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3317
3317
3318 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3318 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3319 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3319 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3320 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3320 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3321 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3321 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3322
3322
3323 def __init__(self, cache_key, cache_args=''):
3323 def __init__(self, cache_key, cache_args=''):
3324 self.cache_key = cache_key
3324 self.cache_key = cache_key
3325 self.cache_args = cache_args
3325 self.cache_args = cache_args
3326 self.cache_active = False
3326 self.cache_active = False
3327
3327
3328 def __unicode__(self):
3328 def __unicode__(self):
3329 return u"<%s('%s:%s[%s]')>" % (
3329 return u"<%s('%s:%s[%s]')>" % (
3330 self.__class__.__name__,
3330 self.__class__.__name__,
3331 self.cache_id, self.cache_key, self.cache_active)
3331 self.cache_id, self.cache_key, self.cache_active)
3332
3332
3333 def _cache_key_partition(self):
3333 def _cache_key_partition(self):
3334 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3334 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3335 return prefix, repo_name, suffix
3335 return prefix, repo_name, suffix
3336
3336
3337 def get_prefix(self):
3337 def get_prefix(self):
3338 """
3338 """
3339 Try to extract prefix from existing cache key. The key could consist
3339 Try to extract prefix from existing cache key. The key could consist
3340 of prefix, repo_name, suffix
3340 of prefix, repo_name, suffix
3341 """
3341 """
3342 # this returns prefix, repo_name, suffix
3342 # this returns prefix, repo_name, suffix
3343 return self._cache_key_partition()[0]
3343 return self._cache_key_partition()[0]
3344
3344
3345 def get_suffix(self):
3345 def get_suffix(self):
3346 """
3346 """
3347 get suffix that might have been used in _get_cache_key to
3347 get suffix that might have been used in _get_cache_key to
3348 generate self.cache_key. Only used for informational purposes
3348 generate self.cache_key. Only used for informational purposes
3349 in repo_edit.mako.
3349 in repo_edit.mako.
3350 """
3350 """
3351 # prefix, repo_name, suffix
3351 # prefix, repo_name, suffix
3352 return self._cache_key_partition()[2]
3352 return self._cache_key_partition()[2]
3353
3353
3354 @classmethod
3354 @classmethod
3355 def delete_all_cache(cls):
3355 def delete_all_cache(cls):
3356 """
3356 """
3357 Delete all cache keys from database.
3357 Delete all cache keys from database.
3358 Should only be run when all instances are down and all entries
3358 Should only be run when all instances are down and all entries
3359 thus stale.
3359 thus stale.
3360 """
3360 """
3361 cls.query().delete()
3361 cls.query().delete()
3362 Session().commit()
3362 Session().commit()
3363
3363
3364 @classmethod
3364 @classmethod
3365 def set_invalidate(cls, cache_uid, delete=False):
3365 def set_invalidate(cls, cache_uid, delete=False):
3366 """
3366 """
3367 Mark all caches of a repo as invalid in the database.
3367 Mark all caches of a repo as invalid in the database.
3368 """
3368 """
3369
3369
3370 try:
3370 try:
3371 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3371 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3372 if delete:
3372 if delete:
3373 qry.delete()
3373 qry.delete()
3374 log.debug('cache objects deleted for cache args %s',
3374 log.debug('cache objects deleted for cache args %s',
3375 safe_str(cache_uid))
3375 safe_str(cache_uid))
3376 else:
3376 else:
3377 qry.update({"cache_active": False})
3377 qry.update({"cache_active": False})
3378 log.debug('cache objects marked as invalid for cache args %s',
3378 log.debug('cache objects marked as invalid for cache args %s',
3379 safe_str(cache_uid))
3379 safe_str(cache_uid))
3380
3380
3381 Session().commit()
3381 Session().commit()
3382 except Exception:
3382 except Exception:
3383 log.exception(
3383 log.exception(
3384 'Cache key invalidation failed for cache args %s',
3384 'Cache key invalidation failed for cache args %s',
3385 safe_str(cache_uid))
3385 safe_str(cache_uid))
3386 Session().rollback()
3386 Session().rollback()
3387
3387
3388 @classmethod
3388 @classmethod
3389 def get_active_cache(cls, cache_key):
3389 def get_active_cache(cls, cache_key):
3390 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3390 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3391 if inv_obj:
3391 if inv_obj:
3392 return inv_obj
3392 return inv_obj
3393 return None
3393 return None
3394
3394
3395
3395
3396 class ChangesetComment(Base, BaseModel):
3396 class ChangesetComment(Base, BaseModel):
3397 __tablename__ = 'changeset_comments'
3397 __tablename__ = 'changeset_comments'
3398 __table_args__ = (
3398 __table_args__ = (
3399 Index('cc_revision_idx', 'revision'),
3399 Index('cc_revision_idx', 'revision'),
3400 base_table_args,
3400 base_table_args,
3401 )
3401 )
3402
3402
3403 COMMENT_OUTDATED = u'comment_outdated'
3403 COMMENT_OUTDATED = u'comment_outdated'
3404 COMMENT_TYPE_NOTE = u'note'
3404 COMMENT_TYPE_NOTE = u'note'
3405 COMMENT_TYPE_TODO = u'todo'
3405 COMMENT_TYPE_TODO = u'todo'
3406 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3406 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3407
3407
3408 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3408 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3409 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3409 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3410 revision = Column('revision', String(40), nullable=True)
3410 revision = Column('revision', String(40), nullable=True)
3411 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3411 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3412 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3412 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3413 line_no = Column('line_no', Unicode(10), nullable=True)
3413 line_no = Column('line_no', Unicode(10), nullable=True)
3414 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3414 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3415 f_path = Column('f_path', Unicode(1000), nullable=True)
3415 f_path = Column('f_path', Unicode(1000), nullable=True)
3416 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3416 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3417 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3417 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3418 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3418 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3419 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3419 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3420 renderer = Column('renderer', Unicode(64), nullable=True)
3420 renderer = Column('renderer', Unicode(64), nullable=True)
3421 display_state = Column('display_state', Unicode(128), nullable=True)
3421 display_state = Column('display_state', Unicode(128), nullable=True)
3422
3422
3423 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3423 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3424 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3424 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3425
3425
3426 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3426 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3427 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3427 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3428
3428
3429 author = relationship('User', lazy='joined')
3429 author = relationship('User', lazy='joined')
3430 repo = relationship('Repository')
3430 repo = relationship('Repository')
3431 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3431 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3432 pull_request = relationship('PullRequest', lazy='joined')
3432 pull_request = relationship('PullRequest', lazy='joined')
3433 pull_request_version = relationship('PullRequestVersion')
3433 pull_request_version = relationship('PullRequestVersion')
3434
3434
3435 @classmethod
3435 @classmethod
3436 def get_users(cls, revision=None, pull_request_id=None):
3436 def get_users(cls, revision=None, pull_request_id=None):
3437 """
3437 """
3438 Returns user associated with this ChangesetComment. ie those
3438 Returns user associated with this ChangesetComment. ie those
3439 who actually commented
3439 who actually commented
3440
3440
3441 :param cls:
3441 :param cls:
3442 :param revision:
3442 :param revision:
3443 """
3443 """
3444 q = Session().query(User)\
3444 q = Session().query(User)\
3445 .join(ChangesetComment.author)
3445 .join(ChangesetComment.author)
3446 if revision:
3446 if revision:
3447 q = q.filter(cls.revision == revision)
3447 q = q.filter(cls.revision == revision)
3448 elif pull_request_id:
3448 elif pull_request_id:
3449 q = q.filter(cls.pull_request_id == pull_request_id)
3449 q = q.filter(cls.pull_request_id == pull_request_id)
3450 return q.all()
3450 return q.all()
3451
3451
3452 @classmethod
3452 @classmethod
3453 def get_index_from_version(cls, pr_version, versions):
3453 def get_index_from_version(cls, pr_version, versions):
3454 num_versions = [x.pull_request_version_id for x in versions]
3454 num_versions = [x.pull_request_version_id for x in versions]
3455 try:
3455 try:
3456 return num_versions.index(pr_version) +1
3456 return num_versions.index(pr_version) +1
3457 except (IndexError, ValueError):
3457 except (IndexError, ValueError):
3458 return
3458 return
3459
3459
3460 @property
3460 @property
3461 def outdated(self):
3461 def outdated(self):
3462 return self.display_state == self.COMMENT_OUTDATED
3462 return self.display_state == self.COMMENT_OUTDATED
3463
3463
3464 def outdated_at_version(self, version):
3464 def outdated_at_version(self, version):
3465 """
3465 """
3466 Checks if comment is outdated for given pull request version
3466 Checks if comment is outdated for given pull request version
3467 """
3467 """
3468 return self.outdated and self.pull_request_version_id != version
3468 return self.outdated and self.pull_request_version_id != version
3469
3469
3470 def older_than_version(self, version):
3470 def older_than_version(self, version):
3471 """
3471 """
3472 Checks if comment is made from previous version than given
3472 Checks if comment is made from previous version than given
3473 """
3473 """
3474 if version is None:
3474 if version is None:
3475 return self.pull_request_version_id is not None
3475 return self.pull_request_version_id is not None
3476
3476
3477 return self.pull_request_version_id < version
3477 return self.pull_request_version_id < version
3478
3478
3479 @property
3479 @property
3480 def resolved(self):
3480 def resolved(self):
3481 return self.resolved_by[0] if self.resolved_by else None
3481 return self.resolved_by[0] if self.resolved_by else None
3482
3482
3483 @property
3483 @property
3484 def is_todo(self):
3484 def is_todo(self):
3485 return self.comment_type == self.COMMENT_TYPE_TODO
3485 return self.comment_type == self.COMMENT_TYPE_TODO
3486
3486
3487 @property
3487 @property
3488 def is_inline(self):
3488 def is_inline(self):
3489 return self.line_no and self.f_path
3489 return self.line_no and self.f_path
3490
3490
3491 def get_index_version(self, versions):
3491 def get_index_version(self, versions):
3492 return self.get_index_from_version(
3492 return self.get_index_from_version(
3493 self.pull_request_version_id, versions)
3493 self.pull_request_version_id, versions)
3494
3494
3495 def __repr__(self):
3495 def __repr__(self):
3496 if self.comment_id:
3496 if self.comment_id:
3497 return '<DB:Comment #%s>' % self.comment_id
3497 return '<DB:Comment #%s>' % self.comment_id
3498 else:
3498 else:
3499 return '<DB:Comment at %#x>' % id(self)
3499 return '<DB:Comment at %#x>' % id(self)
3500
3500
3501 def get_api_data(self):
3501 def get_api_data(self):
3502 comment = self
3502 comment = self
3503 data = {
3503 data = {
3504 'comment_id': comment.comment_id,
3504 'comment_id': comment.comment_id,
3505 'comment_type': comment.comment_type,
3505 'comment_type': comment.comment_type,
3506 'comment_text': comment.text,
3506 'comment_text': comment.text,
3507 'comment_status': comment.status_change,
3507 'comment_status': comment.status_change,
3508 'comment_f_path': comment.f_path,
3508 'comment_f_path': comment.f_path,
3509 'comment_lineno': comment.line_no,
3509 'comment_lineno': comment.line_no,
3510 'comment_author': comment.author,
3510 'comment_author': comment.author,
3511 'comment_created_on': comment.created_on,
3511 'comment_created_on': comment.created_on,
3512 'comment_resolved_by': self.resolved
3512 'comment_resolved_by': self.resolved
3513 }
3513 }
3514 return data
3514 return data
3515
3515
3516 def __json__(self):
3516 def __json__(self):
3517 data = dict()
3517 data = dict()
3518 data.update(self.get_api_data())
3518 data.update(self.get_api_data())
3519 return data
3519 return data
3520
3520
3521
3521
3522 class ChangesetStatus(Base, BaseModel):
3522 class ChangesetStatus(Base, BaseModel):
3523 __tablename__ = 'changeset_statuses'
3523 __tablename__ = 'changeset_statuses'
3524 __table_args__ = (
3524 __table_args__ = (
3525 Index('cs_revision_idx', 'revision'),
3525 Index('cs_revision_idx', 'revision'),
3526 Index('cs_version_idx', 'version'),
3526 Index('cs_version_idx', 'version'),
3527 UniqueConstraint('repo_id', 'revision', 'version'),
3527 UniqueConstraint('repo_id', 'revision', 'version'),
3528 base_table_args
3528 base_table_args
3529 )
3529 )
3530
3530
3531 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3531 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3532 STATUS_APPROVED = 'approved'
3532 STATUS_APPROVED = 'approved'
3533 STATUS_REJECTED = 'rejected'
3533 STATUS_REJECTED = 'rejected'
3534 STATUS_UNDER_REVIEW = 'under_review'
3534 STATUS_UNDER_REVIEW = 'under_review'
3535
3535
3536 STATUSES = [
3536 STATUSES = [
3537 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3537 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3538 (STATUS_APPROVED, _("Approved")),
3538 (STATUS_APPROVED, _("Approved")),
3539 (STATUS_REJECTED, _("Rejected")),
3539 (STATUS_REJECTED, _("Rejected")),
3540 (STATUS_UNDER_REVIEW, _("Under Review")),
3540 (STATUS_UNDER_REVIEW, _("Under Review")),
3541 ]
3541 ]
3542
3542
3543 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3543 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3544 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3544 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3546 revision = Column('revision', String(40), nullable=False)
3546 revision = Column('revision', String(40), nullable=False)
3547 status = Column('status', String(128), nullable=False, default=DEFAULT)
3547 status = Column('status', String(128), nullable=False, default=DEFAULT)
3548 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3548 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3549 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3549 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3550 version = Column('version', Integer(), nullable=False, default=0)
3550 version = Column('version', Integer(), nullable=False, default=0)
3551 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3551 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3552
3552
3553 author = relationship('User', lazy='joined')
3553 author = relationship('User', lazy='joined')
3554 repo = relationship('Repository')
3554 repo = relationship('Repository')
3555 comment = relationship('ChangesetComment', lazy='joined')
3555 comment = relationship('ChangesetComment', lazy='joined')
3556 pull_request = relationship('PullRequest', lazy='joined')
3556 pull_request = relationship('PullRequest', lazy='joined')
3557
3557
3558 def __unicode__(self):
3558 def __unicode__(self):
3559 return u"<%s('%s[v%s]:%s')>" % (
3559 return u"<%s('%s[v%s]:%s')>" % (
3560 self.__class__.__name__,
3560 self.__class__.__name__,
3561 self.status, self.version, self.author
3561 self.status, self.version, self.author
3562 )
3562 )
3563
3563
3564 @classmethod
3564 @classmethod
3565 def get_status_lbl(cls, value):
3565 def get_status_lbl(cls, value):
3566 return dict(cls.STATUSES).get(value)
3566 return dict(cls.STATUSES).get(value)
3567
3567
3568 @property
3568 @property
3569 def status_lbl(self):
3569 def status_lbl(self):
3570 return ChangesetStatus.get_status_lbl(self.status)
3570 return ChangesetStatus.get_status_lbl(self.status)
3571
3571
3572 def get_api_data(self):
3572 def get_api_data(self):
3573 status = self
3573 status = self
3574 data = {
3574 data = {
3575 'status_id': status.changeset_status_id,
3575 'status_id': status.changeset_status_id,
3576 'status': status.status,
3576 'status': status.status,
3577 }
3577 }
3578 return data
3578 return data
3579
3579
3580 def __json__(self):
3580 def __json__(self):
3581 data = dict()
3581 data = dict()
3582 data.update(self.get_api_data())
3582 data.update(self.get_api_data())
3583 return data
3583 return data
3584
3584
3585
3585
3586 class _SetState(object):
3586 class _SetState(object):
3587 """
3587 """
3588 Context processor allowing changing state for sensitive operation such as
3588 Context processor allowing changing state for sensitive operation such as
3589 pull request update or merge
3589 pull request update or merge
3590 """
3590 """
3591
3591
3592 def __init__(self, pull_request, pr_state, back_state=None):
3592 def __init__(self, pull_request, pr_state, back_state=None):
3593 self._pr = pull_request
3593 self._pr = pull_request
3594 self._org_state = back_state or pull_request.pull_request_state
3594 self._org_state = back_state or pull_request.pull_request_state
3595 self._pr_state = pr_state
3595 self._pr_state = pr_state
3596
3596
3597 def __enter__(self):
3597 def __enter__(self):
3598 log.debug('StateLock: entering set state context, setting state to: `%s`',
3598 log.debug('StateLock: entering set state context, setting state to: `%s`',
3599 self._pr_state)
3599 self._pr_state)
3600 self._pr.pull_request_state = self._pr_state
3600 self._pr.pull_request_state = self._pr_state
3601 Session().add(self._pr)
3601 Session().add(self._pr)
3602 Session().commit()
3602 Session().commit()
3603
3603
3604 def __exit__(self, exc_type, exc_val, exc_tb):
3604 def __exit__(self, exc_type, exc_val, exc_tb):
3605 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3605 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3606 self._org_state)
3606 self._org_state)
3607 self._pr.pull_request_state = self._org_state
3607 self._pr.pull_request_state = self._org_state
3608 Session().add(self._pr)
3608 Session().add(self._pr)
3609 Session().commit()
3609 Session().commit()
3610
3610
3611
3611
3612 class _PullRequestBase(BaseModel):
3612 class _PullRequestBase(BaseModel):
3613 """
3613 """
3614 Common attributes of pull request and version entries.
3614 Common attributes of pull request and version entries.
3615 """
3615 """
3616
3616
3617 # .status values
3617 # .status values
3618 STATUS_NEW = u'new'
3618 STATUS_NEW = u'new'
3619 STATUS_OPEN = u'open'
3619 STATUS_OPEN = u'open'
3620 STATUS_CLOSED = u'closed'
3620 STATUS_CLOSED = u'closed'
3621
3621
3622 # available states
3622 # available states
3623 STATE_CREATING = u'creating'
3623 STATE_CREATING = u'creating'
3624 STATE_UPDATING = u'updating'
3624 STATE_UPDATING = u'updating'
3625 STATE_MERGING = u'merging'
3625 STATE_MERGING = u'merging'
3626 STATE_CREATED = u'created'
3626 STATE_CREATED = u'created'
3627
3627
3628 title = Column('title', Unicode(255), nullable=True)
3628 title = Column('title', Unicode(255), nullable=True)
3629 description = Column(
3629 description = Column(
3630 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3630 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3631 nullable=True)
3631 nullable=True)
3632 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3632 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3633
3633
3634 # new/open/closed status of pull request (not approve/reject/etc)
3634 # new/open/closed status of pull request (not approve/reject/etc)
3635 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3635 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3636 created_on = Column(
3636 created_on = Column(
3637 'created_on', DateTime(timezone=False), nullable=False,
3637 'created_on', DateTime(timezone=False), nullable=False,
3638 default=datetime.datetime.now)
3638 default=datetime.datetime.now)
3639 updated_on = Column(
3639 updated_on = Column(
3640 'updated_on', DateTime(timezone=False), nullable=False,
3640 'updated_on', DateTime(timezone=False), nullable=False,
3641 default=datetime.datetime.now)
3641 default=datetime.datetime.now)
3642
3642
3643 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3643 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3644
3644
3645 @declared_attr
3645 @declared_attr
3646 def user_id(cls):
3646 def user_id(cls):
3647 return Column(
3647 return Column(
3648 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3648 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3649 unique=None)
3649 unique=None)
3650
3650
3651 # 500 revisions max
3651 # 500 revisions max
3652 _revisions = Column(
3652 _revisions = Column(
3653 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3653 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3654
3654
3655 @declared_attr
3655 @declared_attr
3656 def source_repo_id(cls):
3656 def source_repo_id(cls):
3657 # TODO: dan: rename column to source_repo_id
3657 # TODO: dan: rename column to source_repo_id
3658 return Column(
3658 return Column(
3659 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3659 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3660 nullable=False)
3660 nullable=False)
3661
3661
3662 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3662 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3663
3663
3664 @hybrid_property
3664 @hybrid_property
3665 def source_ref(self):
3665 def source_ref(self):
3666 return self._source_ref
3666 return self._source_ref
3667
3667
3668 @source_ref.setter
3668 @source_ref.setter
3669 def source_ref(self, val):
3669 def source_ref(self, val):
3670 parts = (val or '').split(':')
3670 parts = (val or '').split(':')
3671 if len(parts) != 3:
3671 if len(parts) != 3:
3672 raise ValueError(
3672 raise ValueError(
3673 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3673 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3674 self._source_ref = safe_unicode(val)
3674 self._source_ref = safe_unicode(val)
3675
3675
3676 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3676 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3677
3677
3678 @hybrid_property
3678 @hybrid_property
3679 def target_ref(self):
3679 def target_ref(self):
3680 return self._target_ref
3680 return self._target_ref
3681
3681
3682 @target_ref.setter
3682 @target_ref.setter
3683 def target_ref(self, val):
3683 def target_ref(self, val):
3684 parts = (val or '').split(':')
3684 parts = (val or '').split(':')
3685 if len(parts) != 3:
3685 if len(parts) != 3:
3686 raise ValueError(
3686 raise ValueError(
3687 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3687 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3688 self._target_ref = safe_unicode(val)
3688 self._target_ref = safe_unicode(val)
3689
3689
3690 @declared_attr
3690 @declared_attr
3691 def target_repo_id(cls):
3691 def target_repo_id(cls):
3692 # TODO: dan: rename column to target_repo_id
3692 # TODO: dan: rename column to target_repo_id
3693 return Column(
3693 return Column(
3694 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3694 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3695 nullable=False)
3695 nullable=False)
3696
3696
3697 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3697 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3698
3698
3699 # TODO: dan: rename column to last_merge_source_rev
3699 # TODO: dan: rename column to last_merge_source_rev
3700 _last_merge_source_rev = Column(
3700 _last_merge_source_rev = Column(
3701 'last_merge_org_rev', String(40), nullable=True)
3701 'last_merge_org_rev', String(40), nullable=True)
3702 # TODO: dan: rename column to last_merge_target_rev
3702 # TODO: dan: rename column to last_merge_target_rev
3703 _last_merge_target_rev = Column(
3703 _last_merge_target_rev = Column(
3704 'last_merge_other_rev', String(40), nullable=True)
3704 'last_merge_other_rev', String(40), nullable=True)
3705 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3705 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3706 merge_rev = Column('merge_rev', String(40), nullable=True)
3706 merge_rev = Column('merge_rev', String(40), nullable=True)
3707
3707
3708 reviewer_data = Column(
3708 reviewer_data = Column(
3709 'reviewer_data_json', MutationObj.as_mutable(
3709 'reviewer_data_json', MutationObj.as_mutable(
3710 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3710 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3711
3711
3712 @property
3712 @property
3713 def reviewer_data_json(self):
3713 def reviewer_data_json(self):
3714 return json.dumps(self.reviewer_data)
3714 return json.dumps(self.reviewer_data)
3715
3715
3716 @hybrid_property
3716 @hybrid_property
3717 def description_safe(self):
3717 def description_safe(self):
3718 from rhodecode.lib import helpers as h
3718 from rhodecode.lib import helpers as h
3719 return h.escape(self.description)
3719 return h.escape(self.description)
3720
3720
3721 @hybrid_property
3721 @hybrid_property
3722 def revisions(self):
3722 def revisions(self):
3723 return self._revisions.split(':') if self._revisions else []
3723 return self._revisions.split(':') if self._revisions else []
3724
3724
3725 @revisions.setter
3725 @revisions.setter
3726 def revisions(self, val):
3726 def revisions(self, val):
3727 self._revisions = ':'.join(val)
3727 self._revisions = ':'.join(val)
3728
3728
3729 @hybrid_property
3729 @hybrid_property
3730 def last_merge_status(self):
3730 def last_merge_status(self):
3731 return safe_int(self._last_merge_status)
3731 return safe_int(self._last_merge_status)
3732
3732
3733 @last_merge_status.setter
3733 @last_merge_status.setter
3734 def last_merge_status(self, val):
3734 def last_merge_status(self, val):
3735 self._last_merge_status = val
3735 self._last_merge_status = val
3736
3736
3737 @declared_attr
3737 @declared_attr
3738 def author(cls):
3738 def author(cls):
3739 return relationship('User', lazy='joined')
3739 return relationship('User', lazy='joined')
3740
3740
3741 @declared_attr
3741 @declared_attr
3742 def source_repo(cls):
3742 def source_repo(cls):
3743 return relationship(
3743 return relationship(
3744 'Repository',
3744 'Repository',
3745 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3745 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3746
3746
3747 @property
3747 @property
3748 def source_ref_parts(self):
3748 def source_ref_parts(self):
3749 return self.unicode_to_reference(self.source_ref)
3749 return self.unicode_to_reference(self.source_ref)
3750
3750
3751 @declared_attr
3751 @declared_attr
3752 def target_repo(cls):
3752 def target_repo(cls):
3753 return relationship(
3753 return relationship(
3754 'Repository',
3754 'Repository',
3755 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3755 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3756
3756
3757 @property
3757 @property
3758 def target_ref_parts(self):
3758 def target_ref_parts(self):
3759 return self.unicode_to_reference(self.target_ref)
3759 return self.unicode_to_reference(self.target_ref)
3760
3760
3761 @property
3761 @property
3762 def shadow_merge_ref(self):
3762 def shadow_merge_ref(self):
3763 return self.unicode_to_reference(self._shadow_merge_ref)
3763 return self.unicode_to_reference(self._shadow_merge_ref)
3764
3764
3765 @shadow_merge_ref.setter
3765 @shadow_merge_ref.setter
3766 def shadow_merge_ref(self, ref):
3766 def shadow_merge_ref(self, ref):
3767 self._shadow_merge_ref = self.reference_to_unicode(ref)
3767 self._shadow_merge_ref = self.reference_to_unicode(ref)
3768
3768
3769 @staticmethod
3769 @staticmethod
3770 def unicode_to_reference(raw):
3770 def unicode_to_reference(raw):
3771 """
3771 """
3772 Convert a unicode (or string) to a reference object.
3772 Convert a unicode (or string) to a reference object.
3773 If unicode evaluates to False it returns None.
3773 If unicode evaluates to False it returns None.
3774 """
3774 """
3775 if raw:
3775 if raw:
3776 refs = raw.split(':')
3776 refs = raw.split(':')
3777 return Reference(*refs)
3777 return Reference(*refs)
3778 else:
3778 else:
3779 return None
3779 return None
3780
3780
3781 @staticmethod
3781 @staticmethod
3782 def reference_to_unicode(ref):
3782 def reference_to_unicode(ref):
3783 """
3783 """
3784 Convert a reference object to unicode.
3784 Convert a reference object to unicode.
3785 If reference is None it returns None.
3785 If reference is None it returns None.
3786 """
3786 """
3787 if ref:
3787 if ref:
3788 return u':'.join(ref)
3788 return u':'.join(ref)
3789 else:
3789 else:
3790 return None
3790 return None
3791
3791
3792 def get_api_data(self, with_merge_state=True):
3792 def get_api_data(self, with_merge_state=True):
3793 from rhodecode.model.pull_request import PullRequestModel
3793 from rhodecode.model.pull_request import PullRequestModel
3794
3794
3795 pull_request = self
3795 pull_request = self
3796 if with_merge_state:
3796 if with_merge_state:
3797 merge_status = PullRequestModel().merge_status(pull_request)
3797 merge_status = PullRequestModel().merge_status(pull_request)
3798 merge_state = {
3798 merge_state = {
3799 'status': merge_status[0],
3799 'status': merge_status[0],
3800 'message': safe_unicode(merge_status[1]),
3800 'message': safe_unicode(merge_status[1]),
3801 }
3801 }
3802 else:
3802 else:
3803 merge_state = {'status': 'not_available',
3803 merge_state = {'status': 'not_available',
3804 'message': 'not_available'}
3804 'message': 'not_available'}
3805
3805
3806 merge_data = {
3806 merge_data = {
3807 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3807 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3808 'reference': (
3808 'reference': (
3809 pull_request.shadow_merge_ref._asdict()
3809 pull_request.shadow_merge_ref._asdict()
3810 if pull_request.shadow_merge_ref else None),
3810 if pull_request.shadow_merge_ref else None),
3811 }
3811 }
3812
3812
3813 data = {
3813 data = {
3814 'pull_request_id': pull_request.pull_request_id,
3814 'pull_request_id': pull_request.pull_request_id,
3815 'url': PullRequestModel().get_url(pull_request),
3815 'url': PullRequestModel().get_url(pull_request),
3816 'title': pull_request.title,
3816 'title': pull_request.title,
3817 'description': pull_request.description,
3817 'description': pull_request.description,
3818 'status': pull_request.status,
3818 'status': pull_request.status,
3819 'state': pull_request.pull_request_state,
3819 'state': pull_request.pull_request_state,
3820 'created_on': pull_request.created_on,
3820 'created_on': pull_request.created_on,
3821 'updated_on': pull_request.updated_on,
3821 'updated_on': pull_request.updated_on,
3822 'commit_ids': pull_request.revisions,
3822 'commit_ids': pull_request.revisions,
3823 'review_status': pull_request.calculated_review_status(),
3823 'review_status': pull_request.calculated_review_status(),
3824 'mergeable': merge_state,
3824 'mergeable': merge_state,
3825 'source': {
3825 'source': {
3826 'clone_url': pull_request.source_repo.clone_url(),
3826 'clone_url': pull_request.source_repo.clone_url(),
3827 'repository': pull_request.source_repo.repo_name,
3827 'repository': pull_request.source_repo.repo_name,
3828 'reference': {
3828 'reference': {
3829 'name': pull_request.source_ref_parts.name,
3829 'name': pull_request.source_ref_parts.name,
3830 'type': pull_request.source_ref_parts.type,
3830 'type': pull_request.source_ref_parts.type,
3831 'commit_id': pull_request.source_ref_parts.commit_id,
3831 'commit_id': pull_request.source_ref_parts.commit_id,
3832 },
3832 },
3833 },
3833 },
3834 'target': {
3834 'target': {
3835 'clone_url': pull_request.target_repo.clone_url(),
3835 'clone_url': pull_request.target_repo.clone_url(),
3836 'repository': pull_request.target_repo.repo_name,
3836 'repository': pull_request.target_repo.repo_name,
3837 'reference': {
3837 'reference': {
3838 'name': pull_request.target_ref_parts.name,
3838 'name': pull_request.target_ref_parts.name,
3839 'type': pull_request.target_ref_parts.type,
3839 'type': pull_request.target_ref_parts.type,
3840 'commit_id': pull_request.target_ref_parts.commit_id,
3840 'commit_id': pull_request.target_ref_parts.commit_id,
3841 },
3841 },
3842 },
3842 },
3843 'merge': merge_data,
3843 'merge': merge_data,
3844 'author': pull_request.author.get_api_data(include_secrets=False,
3844 'author': pull_request.author.get_api_data(include_secrets=False,
3845 details='basic'),
3845 details='basic'),
3846 'reviewers': [
3846 'reviewers': [
3847 {
3847 {
3848 'user': reviewer.get_api_data(include_secrets=False,
3848 'user': reviewer.get_api_data(include_secrets=False,
3849 details='basic'),
3849 details='basic'),
3850 'reasons': reasons,
3850 'reasons': reasons,
3851 'review_status': st[0][1].status if st else 'not_reviewed',
3851 'review_status': st[0][1].status if st else 'not_reviewed',
3852 }
3852 }
3853 for obj, reviewer, reasons, mandatory, st in
3853 for obj, reviewer, reasons, mandatory, st in
3854 pull_request.reviewers_statuses()
3854 pull_request.reviewers_statuses()
3855 ]
3855 ]
3856 }
3856 }
3857
3857
3858 return data
3858 return data
3859
3859
3860 def set_state(self, pull_request_state, final_state=None):
3860 def set_state(self, pull_request_state, final_state=None):
3861 """
3861 """
3862 # goes from initial state to updating to initial state.
3862 # goes from initial state to updating to initial state.
3863 # initial state can be changed by specifying back_state=
3863 # initial state can be changed by specifying back_state=
3864 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
3864 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
3865 pull_request.merge()
3865 pull_request.merge()
3866
3866
3867 :param pull_request_state:
3867 :param pull_request_state:
3868 :param final_state:
3868 :param final_state:
3869
3869
3870 """
3870 """
3871
3871
3872 return _SetState(self, pull_request_state, back_state=final_state)
3872 return _SetState(self, pull_request_state, back_state=final_state)
3873
3873
3874
3874
3875 class PullRequest(Base, _PullRequestBase):
3875 class PullRequest(Base, _PullRequestBase):
3876 __tablename__ = 'pull_requests'
3876 __tablename__ = 'pull_requests'
3877 __table_args__ = (
3877 __table_args__ = (
3878 base_table_args,
3878 base_table_args,
3879 )
3879 )
3880
3880
3881 pull_request_id = Column(
3881 pull_request_id = Column(
3882 'pull_request_id', Integer(), nullable=False, primary_key=True)
3882 'pull_request_id', Integer(), nullable=False, primary_key=True)
3883
3883
3884 def __repr__(self):
3884 def __repr__(self):
3885 if self.pull_request_id:
3885 if self.pull_request_id:
3886 return '<DB:PullRequest #%s>' % self.pull_request_id
3886 return '<DB:PullRequest #%s>' % self.pull_request_id
3887 else:
3887 else:
3888 return '<DB:PullRequest at %#x>' % id(self)
3888 return '<DB:PullRequest at %#x>' % id(self)
3889
3889
3890 reviewers = relationship('PullRequestReviewers',
3890 reviewers = relationship('PullRequestReviewers',
3891 cascade="all, delete, delete-orphan")
3891 cascade="all, delete, delete-orphan")
3892 statuses = relationship('ChangesetStatus',
3892 statuses = relationship('ChangesetStatus',
3893 cascade="all, delete, delete-orphan")
3893 cascade="all, delete, delete-orphan")
3894 comments = relationship('ChangesetComment',
3894 comments = relationship('ChangesetComment',
3895 cascade="all, delete, delete-orphan")
3895 cascade="all, delete, delete-orphan")
3896 versions = relationship('PullRequestVersion',
3896 versions = relationship('PullRequestVersion',
3897 cascade="all, delete, delete-orphan",
3897 cascade="all, delete, delete-orphan",
3898 lazy='dynamic')
3898 lazy='dynamic')
3899
3899
3900 @classmethod
3900 @classmethod
3901 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3901 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3902 internal_methods=None):
3902 internal_methods=None):
3903
3903
3904 class PullRequestDisplay(object):
3904 class PullRequestDisplay(object):
3905 """
3905 """
3906 Special object wrapper for showing PullRequest data via Versions
3906 Special object wrapper for showing PullRequest data via Versions
3907 It mimics PR object as close as possible. This is read only object
3907 It mimics PR object as close as possible. This is read only object
3908 just for display
3908 just for display
3909 """
3909 """
3910
3910
3911 def __init__(self, attrs, internal=None):
3911 def __init__(self, attrs, internal=None):
3912 self.attrs = attrs
3912 self.attrs = attrs
3913 # internal have priority over the given ones via attrs
3913 # internal have priority over the given ones via attrs
3914 self.internal = internal or ['versions']
3914 self.internal = internal or ['versions']
3915
3915
3916 def __getattr__(self, item):
3916 def __getattr__(self, item):
3917 if item in self.internal:
3917 if item in self.internal:
3918 return getattr(self, item)
3918 return getattr(self, item)
3919 try:
3919 try:
3920 return self.attrs[item]
3920 return self.attrs[item]
3921 except KeyError:
3921 except KeyError:
3922 raise AttributeError(
3922 raise AttributeError(
3923 '%s object has no attribute %s' % (self, item))
3923 '%s object has no attribute %s' % (self, item))
3924
3924
3925 def __repr__(self):
3925 def __repr__(self):
3926 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3926 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3927
3927
3928 def versions(self):
3928 def versions(self):
3929 return pull_request_obj.versions.order_by(
3929 return pull_request_obj.versions.order_by(
3930 PullRequestVersion.pull_request_version_id).all()
3930 PullRequestVersion.pull_request_version_id).all()
3931
3931
3932 def is_closed(self):
3932 def is_closed(self):
3933 return pull_request_obj.is_closed()
3933 return pull_request_obj.is_closed()
3934
3934
3935 @property
3935 @property
3936 def pull_request_version_id(self):
3936 def pull_request_version_id(self):
3937 return getattr(pull_request_obj, 'pull_request_version_id', None)
3937 return getattr(pull_request_obj, 'pull_request_version_id', None)
3938
3938
3939 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3939 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3940
3940
3941 attrs.author = StrictAttributeDict(
3941 attrs.author = StrictAttributeDict(
3942 pull_request_obj.author.get_api_data())
3942 pull_request_obj.author.get_api_data())
3943 if pull_request_obj.target_repo:
3943 if pull_request_obj.target_repo:
3944 attrs.target_repo = StrictAttributeDict(
3944 attrs.target_repo = StrictAttributeDict(
3945 pull_request_obj.target_repo.get_api_data())
3945 pull_request_obj.target_repo.get_api_data())
3946 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3946 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3947
3947
3948 if pull_request_obj.source_repo:
3948 if pull_request_obj.source_repo:
3949 attrs.source_repo = StrictAttributeDict(
3949 attrs.source_repo = StrictAttributeDict(
3950 pull_request_obj.source_repo.get_api_data())
3950 pull_request_obj.source_repo.get_api_data())
3951 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3951 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3952
3952
3953 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3953 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3954 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3954 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3955 attrs.revisions = pull_request_obj.revisions
3955 attrs.revisions = pull_request_obj.revisions
3956
3956
3957 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3957 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3958 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3958 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3959 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3959 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3960
3960
3961 return PullRequestDisplay(attrs, internal=internal_methods)
3961 return PullRequestDisplay(attrs, internal=internal_methods)
3962
3962
3963 def is_closed(self):
3963 def is_closed(self):
3964 return self.status == self.STATUS_CLOSED
3964 return self.status == self.STATUS_CLOSED
3965
3965
3966 def __json__(self):
3966 def __json__(self):
3967 return {
3967 return {
3968 'revisions': self.revisions,
3968 'revisions': self.revisions,
3969 }
3969 }
3970
3970
3971 def calculated_review_status(self):
3971 def calculated_review_status(self):
3972 from rhodecode.model.changeset_status import ChangesetStatusModel
3972 from rhodecode.model.changeset_status import ChangesetStatusModel
3973 return ChangesetStatusModel().calculated_review_status(self)
3973 return ChangesetStatusModel().calculated_review_status(self)
3974
3974
3975 def reviewers_statuses(self):
3975 def reviewers_statuses(self):
3976 from rhodecode.model.changeset_status import ChangesetStatusModel
3976 from rhodecode.model.changeset_status import ChangesetStatusModel
3977 return ChangesetStatusModel().reviewers_statuses(self)
3977 return ChangesetStatusModel().reviewers_statuses(self)
3978
3978
3979 @property
3979 @property
3980 def workspace_id(self):
3980 def workspace_id(self):
3981 from rhodecode.model.pull_request import PullRequestModel
3981 from rhodecode.model.pull_request import PullRequestModel
3982 return PullRequestModel()._workspace_id(self)
3982 return PullRequestModel()._workspace_id(self)
3983
3983
3984 def get_shadow_repo(self):
3984 def get_shadow_repo(self):
3985 workspace_id = self.workspace_id
3985 workspace_id = self.workspace_id
3986 vcs_obj = self.target_repo.scm_instance()
3986 vcs_obj = self.target_repo.scm_instance()
3987 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3987 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3988 self.target_repo.repo_id, workspace_id)
3988 self.target_repo.repo_id, workspace_id)
3989 if os.path.isdir(shadow_repository_path):
3989 if os.path.isdir(shadow_repository_path):
3990 return vcs_obj._get_shadow_instance(shadow_repository_path)
3990 return vcs_obj._get_shadow_instance(shadow_repository_path)
3991
3991
3992
3992
3993 class PullRequestVersion(Base, _PullRequestBase):
3993 class PullRequestVersion(Base, _PullRequestBase):
3994 __tablename__ = 'pull_request_versions'
3994 __tablename__ = 'pull_request_versions'
3995 __table_args__ = (
3995 __table_args__ = (
3996 base_table_args,
3996 base_table_args,
3997 )
3997 )
3998
3998
3999 pull_request_version_id = Column(
3999 pull_request_version_id = Column(
4000 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4000 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4001 pull_request_id = Column(
4001 pull_request_id = Column(
4002 'pull_request_id', Integer(),
4002 'pull_request_id', Integer(),
4003 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4003 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4004 pull_request = relationship('PullRequest')
4004 pull_request = relationship('PullRequest')
4005
4005
4006 def __repr__(self):
4006 def __repr__(self):
4007 if self.pull_request_version_id:
4007 if self.pull_request_version_id:
4008 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4008 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4009 else:
4009 else:
4010 return '<DB:PullRequestVersion at %#x>' % id(self)
4010 return '<DB:PullRequestVersion at %#x>' % id(self)
4011
4011
4012 @property
4012 @property
4013 def reviewers(self):
4013 def reviewers(self):
4014 return self.pull_request.reviewers
4014 return self.pull_request.reviewers
4015
4015
4016 @property
4016 @property
4017 def versions(self):
4017 def versions(self):
4018 return self.pull_request.versions
4018 return self.pull_request.versions
4019
4019
4020 def is_closed(self):
4020 def is_closed(self):
4021 # calculate from original
4021 # calculate from original
4022 return self.pull_request.status == self.STATUS_CLOSED
4022 return self.pull_request.status == self.STATUS_CLOSED
4023
4023
4024 def calculated_review_status(self):
4024 def calculated_review_status(self):
4025 return self.pull_request.calculated_review_status()
4025 return self.pull_request.calculated_review_status()
4026
4026
4027 def reviewers_statuses(self):
4027 def reviewers_statuses(self):
4028 return self.pull_request.reviewers_statuses()
4028 return self.pull_request.reviewers_statuses()
4029
4029
4030
4030
4031 class PullRequestReviewers(Base, BaseModel):
4031 class PullRequestReviewers(Base, BaseModel):
4032 __tablename__ = 'pull_request_reviewers'
4032 __tablename__ = 'pull_request_reviewers'
4033 __table_args__ = (
4033 __table_args__ = (
4034 base_table_args,
4034 base_table_args,
4035 )
4035 )
4036
4036
4037 @hybrid_property
4037 @hybrid_property
4038 def reasons(self):
4038 def reasons(self):
4039 if not self._reasons:
4039 if not self._reasons:
4040 return []
4040 return []
4041 return self._reasons
4041 return self._reasons
4042
4042
4043 @reasons.setter
4043 @reasons.setter
4044 def reasons(self, val):
4044 def reasons(self, val):
4045 val = val or []
4045 val = val or []
4046 if any(not isinstance(x, compat.string_types) for x in val):
4046 if any(not isinstance(x, compat.string_types) for x in val):
4047 raise Exception('invalid reasons type, must be list of strings')
4047 raise Exception('invalid reasons type, must be list of strings')
4048 self._reasons = val
4048 self._reasons = val
4049
4049
4050 pull_requests_reviewers_id = Column(
4050 pull_requests_reviewers_id = Column(
4051 'pull_requests_reviewers_id', Integer(), nullable=False,
4051 'pull_requests_reviewers_id', Integer(), nullable=False,
4052 primary_key=True)
4052 primary_key=True)
4053 pull_request_id = Column(
4053 pull_request_id = Column(
4054 "pull_request_id", Integer(),
4054 "pull_request_id", Integer(),
4055 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4055 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4056 user_id = Column(
4056 user_id = Column(
4057 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4057 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4058 _reasons = Column(
4058 _reasons = Column(
4059 'reason', MutationList.as_mutable(
4059 'reason', MutationList.as_mutable(
4060 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4060 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4061
4061
4062 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4062 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4063 user = relationship('User')
4063 user = relationship('User')
4064 pull_request = relationship('PullRequest')
4064 pull_request = relationship('PullRequest')
4065
4065
4066 rule_data = Column(
4066 rule_data = Column(
4067 'rule_data_json',
4067 'rule_data_json',
4068 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4068 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4069
4069
4070 def rule_user_group_data(self):
4070 def rule_user_group_data(self):
4071 """
4071 """
4072 Returns the voting user group rule data for this reviewer
4072 Returns the voting user group rule data for this reviewer
4073 """
4073 """
4074
4074
4075 if self.rule_data and 'vote_rule' in self.rule_data:
4075 if self.rule_data and 'vote_rule' in self.rule_data:
4076 user_group_data = {}
4076 user_group_data = {}
4077 if 'rule_user_group_entry_id' in self.rule_data:
4077 if 'rule_user_group_entry_id' in self.rule_data:
4078 # means a group with voting rules !
4078 # means a group with voting rules !
4079 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4079 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4080 user_group_data['name'] = self.rule_data['rule_name']
4080 user_group_data['name'] = self.rule_data['rule_name']
4081 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4081 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4082
4082
4083 return user_group_data
4083 return user_group_data
4084
4084
4085 def __unicode__(self):
4085 def __unicode__(self):
4086 return u"<%s('id:%s')>" % (self.__class__.__name__,
4086 return u"<%s('id:%s')>" % (self.__class__.__name__,
4087 self.pull_requests_reviewers_id)
4087 self.pull_requests_reviewers_id)
4088
4088
4089
4089
4090 class Notification(Base, BaseModel):
4090 class Notification(Base, BaseModel):
4091 __tablename__ = 'notifications'
4091 __tablename__ = 'notifications'
4092 __table_args__ = (
4092 __table_args__ = (
4093 Index('notification_type_idx', 'type'),
4093 Index('notification_type_idx', 'type'),
4094 base_table_args,
4094 base_table_args,
4095 )
4095 )
4096
4096
4097 TYPE_CHANGESET_COMMENT = u'cs_comment'
4097 TYPE_CHANGESET_COMMENT = u'cs_comment'
4098 TYPE_MESSAGE = u'message'
4098 TYPE_MESSAGE = u'message'
4099 TYPE_MENTION = u'mention'
4099 TYPE_MENTION = u'mention'
4100 TYPE_REGISTRATION = u'registration'
4100 TYPE_REGISTRATION = u'registration'
4101 TYPE_PULL_REQUEST = u'pull_request'
4101 TYPE_PULL_REQUEST = u'pull_request'
4102 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4102 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4103
4103
4104 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4104 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4105 subject = Column('subject', Unicode(512), nullable=True)
4105 subject = Column('subject', Unicode(512), nullable=True)
4106 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4106 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4107 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4107 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4108 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4108 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4109 type_ = Column('type', Unicode(255))
4109 type_ = Column('type', Unicode(255))
4110
4110
4111 created_by_user = relationship('User')
4111 created_by_user = relationship('User')
4112 notifications_to_users = relationship('UserNotification', lazy='joined',
4112 notifications_to_users = relationship('UserNotification', lazy='joined',
4113 cascade="all, delete, delete-orphan")
4113 cascade="all, delete, delete-orphan")
4114
4114
4115 @property
4115 @property
4116 def recipients(self):
4116 def recipients(self):
4117 return [x.user for x in UserNotification.query()\
4117 return [x.user for x in UserNotification.query()\
4118 .filter(UserNotification.notification == self)\
4118 .filter(UserNotification.notification == self)\
4119 .order_by(UserNotification.user_id.asc()).all()]
4119 .order_by(UserNotification.user_id.asc()).all()]
4120
4120
4121 @classmethod
4121 @classmethod
4122 def create(cls, created_by, subject, body, recipients, type_=None):
4122 def create(cls, created_by, subject, body, recipients, type_=None):
4123 if type_ is None:
4123 if type_ is None:
4124 type_ = Notification.TYPE_MESSAGE
4124 type_ = Notification.TYPE_MESSAGE
4125
4125
4126 notification = cls()
4126 notification = cls()
4127 notification.created_by_user = created_by
4127 notification.created_by_user = created_by
4128 notification.subject = subject
4128 notification.subject = subject
4129 notification.body = body
4129 notification.body = body
4130 notification.type_ = type_
4130 notification.type_ = type_
4131 notification.created_on = datetime.datetime.now()
4131 notification.created_on = datetime.datetime.now()
4132
4132
4133 # For each recipient link the created notification to his account
4133 # For each recipient link the created notification to his account
4134 for u in recipients:
4134 for u in recipients:
4135 assoc = UserNotification()
4135 assoc = UserNotification()
4136 assoc.user_id = u.user_id
4136 assoc.user_id = u.user_id
4137 assoc.notification = notification
4137 assoc.notification = notification
4138
4138
4139 # if created_by is inside recipients mark his notification
4139 # if created_by is inside recipients mark his notification
4140 # as read
4140 # as read
4141 if u.user_id == created_by.user_id:
4141 if u.user_id == created_by.user_id:
4142 assoc.read = True
4142 assoc.read = True
4143 Session().add(assoc)
4143 Session().add(assoc)
4144
4144
4145 Session().add(notification)
4145 Session().add(notification)
4146
4146
4147 return notification
4147 return notification
4148
4148
4149
4149
4150 class UserNotification(Base, BaseModel):
4150 class UserNotification(Base, BaseModel):
4151 __tablename__ = 'user_to_notification'
4151 __tablename__ = 'user_to_notification'
4152 __table_args__ = (
4152 __table_args__ = (
4153 UniqueConstraint('user_id', 'notification_id'),
4153 UniqueConstraint('user_id', 'notification_id'),
4154 base_table_args
4154 base_table_args
4155 )
4155 )
4156
4156
4157 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4157 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4158 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4158 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4159 read = Column('read', Boolean, default=False)
4159 read = Column('read', Boolean, default=False)
4160 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4160 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4161
4161
4162 user = relationship('User', lazy="joined")
4162 user = relationship('User', lazy="joined")
4163 notification = relationship('Notification', lazy="joined",
4163 notification = relationship('Notification', lazy="joined",
4164 order_by=lambda: Notification.created_on.desc(),)
4164 order_by=lambda: Notification.created_on.desc(),)
4165
4165
4166 def mark_as_read(self):
4166 def mark_as_read(self):
4167 self.read = True
4167 self.read = True
4168 Session().add(self)
4168 Session().add(self)
4169
4169
4170
4170
4171 class Gist(Base, BaseModel):
4171 class Gist(Base, BaseModel):
4172 __tablename__ = 'gists'
4172 __tablename__ = 'gists'
4173 __table_args__ = (
4173 __table_args__ = (
4174 Index('g_gist_access_id_idx', 'gist_access_id'),
4174 Index('g_gist_access_id_idx', 'gist_access_id'),
4175 Index('g_created_on_idx', 'created_on'),
4175 Index('g_created_on_idx', 'created_on'),
4176 base_table_args
4176 base_table_args
4177 )
4177 )
4178
4178
4179 GIST_PUBLIC = u'public'
4179 GIST_PUBLIC = u'public'
4180 GIST_PRIVATE = u'private'
4180 GIST_PRIVATE = u'private'
4181 DEFAULT_FILENAME = u'gistfile1.txt'
4181 DEFAULT_FILENAME = u'gistfile1.txt'
4182
4182
4183 ACL_LEVEL_PUBLIC = u'acl_public'
4183 ACL_LEVEL_PUBLIC = u'acl_public'
4184 ACL_LEVEL_PRIVATE = u'acl_private'
4184 ACL_LEVEL_PRIVATE = u'acl_private'
4185
4185
4186 gist_id = Column('gist_id', Integer(), primary_key=True)
4186 gist_id = Column('gist_id', Integer(), primary_key=True)
4187 gist_access_id = Column('gist_access_id', Unicode(250))
4187 gist_access_id = Column('gist_access_id', Unicode(250))
4188 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4188 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4189 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4189 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4190 gist_expires = Column('gist_expires', Float(53), nullable=False)
4190 gist_expires = Column('gist_expires', Float(53), nullable=False)
4191 gist_type = Column('gist_type', Unicode(128), nullable=False)
4191 gist_type = Column('gist_type', Unicode(128), nullable=False)
4192 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4192 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4193 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4193 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4194 acl_level = Column('acl_level', Unicode(128), nullable=True)
4194 acl_level = Column('acl_level', Unicode(128), nullable=True)
4195
4195
4196 owner = relationship('User')
4196 owner = relationship('User')
4197
4197
4198 def __repr__(self):
4198 def __repr__(self):
4199 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4199 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4200
4200
4201 @hybrid_property
4201 @hybrid_property
4202 def description_safe(self):
4202 def description_safe(self):
4203 from rhodecode.lib import helpers as h
4203 from rhodecode.lib import helpers as h
4204 return h.escape(self.gist_description)
4204 return h.escape(self.gist_description)
4205
4205
4206 @classmethod
4206 @classmethod
4207 def get_or_404(cls, id_):
4207 def get_or_404(cls, id_):
4208 from pyramid.httpexceptions import HTTPNotFound
4208 from pyramid.httpexceptions import HTTPNotFound
4209
4209
4210 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4210 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4211 if not res:
4211 if not res:
4212 raise HTTPNotFound()
4212 raise HTTPNotFound()
4213 return res
4213 return res
4214
4214
4215 @classmethod
4215 @classmethod
4216 def get_by_access_id(cls, gist_access_id):
4216 def get_by_access_id(cls, gist_access_id):
4217 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4217 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4218
4218
4219 def gist_url(self):
4219 def gist_url(self):
4220 from rhodecode.model.gist import GistModel
4220 from rhodecode.model.gist import GistModel
4221 return GistModel().get_url(self)
4221 return GistModel().get_url(self)
4222
4222
4223 @classmethod
4223 @classmethod
4224 def base_path(cls):
4224 def base_path(cls):
4225 """
4225 """
4226 Returns base path when all gists are stored
4226 Returns base path when all gists are stored
4227
4227
4228 :param cls:
4228 :param cls:
4229 """
4229 """
4230 from rhodecode.model.gist import GIST_STORE_LOC
4230 from rhodecode.model.gist import GIST_STORE_LOC
4231 q = Session().query(RhodeCodeUi)\
4231 q = Session().query(RhodeCodeUi)\
4232 .filter(RhodeCodeUi.ui_key == URL_SEP)
4232 .filter(RhodeCodeUi.ui_key == URL_SEP)
4233 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4233 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4234 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4234 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4235
4235
4236 def get_api_data(self):
4236 def get_api_data(self):
4237 """
4237 """
4238 Common function for generating gist related data for API
4238 Common function for generating gist related data for API
4239 """
4239 """
4240 gist = self
4240 gist = self
4241 data = {
4241 data = {
4242 'gist_id': gist.gist_id,
4242 'gist_id': gist.gist_id,
4243 'type': gist.gist_type,
4243 'type': gist.gist_type,
4244 'access_id': gist.gist_access_id,
4244 'access_id': gist.gist_access_id,
4245 'description': gist.gist_description,
4245 'description': gist.gist_description,
4246 'url': gist.gist_url(),
4246 'url': gist.gist_url(),
4247 'expires': gist.gist_expires,
4247 'expires': gist.gist_expires,
4248 'created_on': gist.created_on,
4248 'created_on': gist.created_on,
4249 'modified_at': gist.modified_at,
4249 'modified_at': gist.modified_at,
4250 'content': None,
4250 'content': None,
4251 'acl_level': gist.acl_level,
4251 'acl_level': gist.acl_level,
4252 }
4252 }
4253 return data
4253 return data
4254
4254
4255 def __json__(self):
4255 def __json__(self):
4256 data = dict(
4256 data = dict(
4257 )
4257 )
4258 data.update(self.get_api_data())
4258 data.update(self.get_api_data())
4259 return data
4259 return data
4260 # SCM functions
4260 # SCM functions
4261
4261
4262 def scm_instance(self, **kwargs):
4262 def scm_instance(self, **kwargs):
4263 """
4263 """
4264 Get explicit Mercurial repository used
4264 Get explicit Mercurial repository used
4265 :param kwargs:
4265 :param kwargs:
4266 :return:
4266 :return:
4267 """
4267 """
4268 from rhodecode.model.gist import GistModel
4268 from rhodecode.model.gist import GistModel
4269 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4269 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4270 return get_vcs_instance(
4270 return get_vcs_instance(
4271 repo_path=safe_str(full_repo_path), create=False,
4271 repo_path=safe_str(full_repo_path), create=False,
4272 _vcs_alias=GistModel.vcs_backend)
4272 _vcs_alias=GistModel.vcs_backend)
4273
4273
4274
4274
4275 class ExternalIdentity(Base, BaseModel):
4275 class ExternalIdentity(Base, BaseModel):
4276 __tablename__ = 'external_identities'
4276 __tablename__ = 'external_identities'
4277 __table_args__ = (
4277 __table_args__ = (
4278 Index('local_user_id_idx', 'local_user_id'),
4278 Index('local_user_id_idx', 'local_user_id'),
4279 Index('external_id_idx', 'external_id'),
4279 Index('external_id_idx', 'external_id'),
4280 base_table_args
4280 base_table_args
4281 )
4281 )
4282
4282
4283 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4283 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4284 external_username = Column('external_username', Unicode(1024), default=u'')
4284 external_username = Column('external_username', Unicode(1024), default=u'')
4285 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4285 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4286 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4286 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4287 access_token = Column('access_token', String(1024), default=u'')
4287 access_token = Column('access_token', String(1024), default=u'')
4288 alt_token = Column('alt_token', String(1024), default=u'')
4288 alt_token = Column('alt_token', String(1024), default=u'')
4289 token_secret = Column('token_secret', String(1024), default=u'')
4289 token_secret = Column('token_secret', String(1024), default=u'')
4290
4290
4291 @classmethod
4291 @classmethod
4292 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4292 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4293 """
4293 """
4294 Returns ExternalIdentity instance based on search params
4294 Returns ExternalIdentity instance based on search params
4295
4295
4296 :param external_id:
4296 :param external_id:
4297 :param provider_name:
4297 :param provider_name:
4298 :return: ExternalIdentity
4298 :return: ExternalIdentity
4299 """
4299 """
4300 query = cls.query()
4300 query = cls.query()
4301 query = query.filter(cls.external_id == external_id)
4301 query = query.filter(cls.external_id == external_id)
4302 query = query.filter(cls.provider_name == provider_name)
4302 query = query.filter(cls.provider_name == provider_name)
4303 if local_user_id:
4303 if local_user_id:
4304 query = query.filter(cls.local_user_id == local_user_id)
4304 query = query.filter(cls.local_user_id == local_user_id)
4305 return query.first()
4305 return query.first()
4306
4306
4307 @classmethod
4307 @classmethod
4308 def user_by_external_id_and_provider(cls, external_id, provider_name):
4308 def user_by_external_id_and_provider(cls, external_id, provider_name):
4309 """
4309 """
4310 Returns User instance based on search params
4310 Returns User instance based on search params
4311
4311
4312 :param external_id:
4312 :param external_id:
4313 :param provider_name:
4313 :param provider_name:
4314 :return: User
4314 :return: User
4315 """
4315 """
4316 query = User.query()
4316 query = User.query()
4317 query = query.filter(cls.external_id == external_id)
4317 query = query.filter(cls.external_id == external_id)
4318 query = query.filter(cls.provider_name == provider_name)
4318 query = query.filter(cls.provider_name == provider_name)
4319 query = query.filter(User.user_id == cls.local_user_id)
4319 query = query.filter(User.user_id == cls.local_user_id)
4320 return query.first()
4320 return query.first()
4321
4321
4322 @classmethod
4322 @classmethod
4323 def by_local_user_id(cls, local_user_id):
4323 def by_local_user_id(cls, local_user_id):
4324 """
4324 """
4325 Returns all tokens for user
4325 Returns all tokens for user
4326
4326
4327 :param local_user_id:
4327 :param local_user_id:
4328 :return: ExternalIdentity
4328 :return: ExternalIdentity
4329 """
4329 """
4330 query = cls.query()
4330 query = cls.query()
4331 query = query.filter(cls.local_user_id == local_user_id)
4331 query = query.filter(cls.local_user_id == local_user_id)
4332 return query
4332 return query
4333
4333
4334 @classmethod
4334 @classmethod
4335 def load_provider_plugin(cls, plugin_id):
4335 def load_provider_plugin(cls, plugin_id):
4336 from rhodecode.authentication.base import loadplugin
4336 from rhodecode.authentication.base import loadplugin
4337 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4337 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4338 auth_plugin = loadplugin(_plugin_id)
4338 auth_plugin = loadplugin(_plugin_id)
4339 return auth_plugin
4339 return auth_plugin
4340
4340
4341
4341
4342 class Integration(Base, BaseModel):
4342 class Integration(Base, BaseModel):
4343 __tablename__ = 'integrations'
4343 __tablename__ = 'integrations'
4344 __table_args__ = (
4344 __table_args__ = (
4345 base_table_args
4345 base_table_args
4346 )
4346 )
4347
4347
4348 integration_id = Column('integration_id', Integer(), primary_key=True)
4348 integration_id = Column('integration_id', Integer(), primary_key=True)
4349 integration_type = Column('integration_type', String(255))
4349 integration_type = Column('integration_type', String(255))
4350 enabled = Column('enabled', Boolean(), nullable=False)
4350 enabled = Column('enabled', Boolean(), nullable=False)
4351 name = Column('name', String(255), nullable=False)
4351 name = Column('name', String(255), nullable=False)
4352 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4352 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4353 default=False)
4353 default=False)
4354
4354
4355 settings = Column(
4355 settings = Column(
4356 'settings_json', MutationObj.as_mutable(
4356 'settings_json', MutationObj.as_mutable(
4357 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4357 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4358 repo_id = Column(
4358 repo_id = Column(
4359 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4359 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4360 nullable=True, unique=None, default=None)
4360 nullable=True, unique=None, default=None)
4361 repo = relationship('Repository', lazy='joined')
4361 repo = relationship('Repository', lazy='joined')
4362
4362
4363 repo_group_id = Column(
4363 repo_group_id = Column(
4364 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4364 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4365 nullable=True, unique=None, default=None)
4365 nullable=True, unique=None, default=None)
4366 repo_group = relationship('RepoGroup', lazy='joined')
4366 repo_group = relationship('RepoGroup', lazy='joined')
4367
4367
4368 @property
4368 @property
4369 def scope(self):
4369 def scope(self):
4370 if self.repo:
4370 if self.repo:
4371 return repr(self.repo)
4371 return repr(self.repo)
4372 if self.repo_group:
4372 if self.repo_group:
4373 if self.child_repos_only:
4373 if self.child_repos_only:
4374 return repr(self.repo_group) + ' (child repos only)'
4374 return repr(self.repo_group) + ' (child repos only)'
4375 else:
4375 else:
4376 return repr(self.repo_group) + ' (recursive)'
4376 return repr(self.repo_group) + ' (recursive)'
4377 if self.child_repos_only:
4377 if self.child_repos_only:
4378 return 'root_repos'
4378 return 'root_repos'
4379 return 'global'
4379 return 'global'
4380
4380
4381 def __repr__(self):
4381 def __repr__(self):
4382 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4382 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4383
4383
4384
4384
4385 class RepoReviewRuleUser(Base, BaseModel):
4385 class RepoReviewRuleUser(Base, BaseModel):
4386 __tablename__ = 'repo_review_rules_users'
4386 __tablename__ = 'repo_review_rules_users'
4387 __table_args__ = (
4387 __table_args__ = (
4388 base_table_args
4388 base_table_args
4389 )
4389 )
4390
4390
4391 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4391 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4392 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4392 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4394 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4394 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4395 user = relationship('User')
4395 user = relationship('User')
4396
4396
4397 def rule_data(self):
4397 def rule_data(self):
4398 return {
4398 return {
4399 'mandatory': self.mandatory
4399 'mandatory': self.mandatory
4400 }
4400 }
4401
4401
4402
4402
4403 class RepoReviewRuleUserGroup(Base, BaseModel):
4403 class RepoReviewRuleUserGroup(Base, BaseModel):
4404 __tablename__ = 'repo_review_rules_users_groups'
4404 __tablename__ = 'repo_review_rules_users_groups'
4405 __table_args__ = (
4405 __table_args__ = (
4406 base_table_args
4406 base_table_args
4407 )
4407 )
4408
4408
4409 VOTE_RULE_ALL = -1
4409 VOTE_RULE_ALL = -1
4410
4410
4411 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4411 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4412 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4412 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4413 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4413 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4414 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4414 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4415 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4415 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4416 users_group = relationship('UserGroup')
4416 users_group = relationship('UserGroup')
4417
4417
4418 def rule_data(self):
4418 def rule_data(self):
4419 return {
4419 return {
4420 'mandatory': self.mandatory,
4420 'mandatory': self.mandatory,
4421 'vote_rule': self.vote_rule
4421 'vote_rule': self.vote_rule
4422 }
4422 }
4423
4423
4424 @property
4424 @property
4425 def vote_rule_label(self):
4425 def vote_rule_label(self):
4426 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4426 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4427 return 'all must vote'
4427 return 'all must vote'
4428 else:
4428 else:
4429 return 'min. vote {}'.format(self.vote_rule)
4429 return 'min. vote {}'.format(self.vote_rule)
4430
4430
4431
4431
4432 class RepoReviewRule(Base, BaseModel):
4432 class RepoReviewRule(Base, BaseModel):
4433 __tablename__ = 'repo_review_rules'
4433 __tablename__ = 'repo_review_rules'
4434 __table_args__ = (
4434 __table_args__ = (
4435 base_table_args
4435 base_table_args
4436 )
4436 )
4437
4437
4438 repo_review_rule_id = Column(
4438 repo_review_rule_id = Column(
4439 'repo_review_rule_id', Integer(), primary_key=True)
4439 'repo_review_rule_id', Integer(), primary_key=True)
4440 repo_id = Column(
4440 repo_id = Column(
4441 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4441 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4442 repo = relationship('Repository', backref='review_rules')
4442 repo = relationship('Repository', backref='review_rules')
4443
4443
4444 review_rule_name = Column('review_rule_name', String(255))
4444 review_rule_name = Column('review_rule_name', String(255))
4445 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4445 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4446 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4446 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4447 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4447 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4448
4448
4449 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4449 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4450 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4450 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4451 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4451 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4452 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4452 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4453
4453
4454 rule_users = relationship('RepoReviewRuleUser')
4454 rule_users = relationship('RepoReviewRuleUser')
4455 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4455 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4456
4456
4457 def _validate_pattern(self, value):
4457 def _validate_pattern(self, value):
4458 re.compile('^' + glob2re(value) + '$')
4458 re.compile('^' + glob2re(value) + '$')
4459
4459
4460 @hybrid_property
4460 @hybrid_property
4461 def source_branch_pattern(self):
4461 def source_branch_pattern(self):
4462 return self._branch_pattern or '*'
4462 return self._branch_pattern or '*'
4463
4463
4464 @source_branch_pattern.setter
4464 @source_branch_pattern.setter
4465 def source_branch_pattern(self, value):
4465 def source_branch_pattern(self, value):
4466 self._validate_pattern(value)
4466 self._validate_pattern(value)
4467 self._branch_pattern = value or '*'
4467 self._branch_pattern = value or '*'
4468
4468
4469 @hybrid_property
4469 @hybrid_property
4470 def target_branch_pattern(self):
4470 def target_branch_pattern(self):
4471 return self._target_branch_pattern or '*'
4471 return self._target_branch_pattern or '*'
4472
4472
4473 @target_branch_pattern.setter
4473 @target_branch_pattern.setter
4474 def target_branch_pattern(self, value):
4474 def target_branch_pattern(self, value):
4475 self._validate_pattern(value)
4475 self._validate_pattern(value)
4476 self._target_branch_pattern = value or '*'
4476 self._target_branch_pattern = value or '*'
4477
4477
4478 @hybrid_property
4478 @hybrid_property
4479 def file_pattern(self):
4479 def file_pattern(self):
4480 return self._file_pattern or '*'
4480 return self._file_pattern or '*'
4481
4481
4482 @file_pattern.setter
4482 @file_pattern.setter
4483 def file_pattern(self, value):
4483 def file_pattern(self, value):
4484 self._validate_pattern(value)
4484 self._validate_pattern(value)
4485 self._file_pattern = value or '*'
4485 self._file_pattern = value or '*'
4486
4486
4487 def matches(self, source_branch, target_branch, files_changed):
4487 def matches(self, source_branch, target_branch, files_changed):
4488 """
4488 """
4489 Check if this review rule matches a branch/files in a pull request
4489 Check if this review rule matches a branch/files in a pull request
4490
4490
4491 :param source_branch: source branch name for the commit
4491 :param source_branch: source branch name for the commit
4492 :param target_branch: target branch name for the commit
4492 :param target_branch: target branch name for the commit
4493 :param files_changed: list of file paths changed in the pull request
4493 :param files_changed: list of file paths changed in the pull request
4494 """
4494 """
4495
4495
4496 source_branch = source_branch or ''
4496 source_branch = source_branch or ''
4497 target_branch = target_branch or ''
4497 target_branch = target_branch or ''
4498 files_changed = files_changed or []
4498 files_changed = files_changed or []
4499
4499
4500 branch_matches = True
4500 branch_matches = True
4501 if source_branch or target_branch:
4501 if source_branch or target_branch:
4502 if self.source_branch_pattern == '*':
4502 if self.source_branch_pattern == '*':
4503 source_branch_match = True
4503 source_branch_match = True
4504 else:
4504 else:
4505 if self.source_branch_pattern.startswith('re:'):
4505 if self.source_branch_pattern.startswith('re:'):
4506 source_pattern = self.source_branch_pattern[3:]
4506 source_pattern = self.source_branch_pattern[3:]
4507 else:
4507 else:
4508 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4508 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4509 source_branch_regex = re.compile(source_pattern)
4509 source_branch_regex = re.compile(source_pattern)
4510 source_branch_match = bool(source_branch_regex.search(source_branch))
4510 source_branch_match = bool(source_branch_regex.search(source_branch))
4511 if self.target_branch_pattern == '*':
4511 if self.target_branch_pattern == '*':
4512 target_branch_match = True
4512 target_branch_match = True
4513 else:
4513 else:
4514 if self.target_branch_pattern.startswith('re:'):
4514 if self.target_branch_pattern.startswith('re:'):
4515 target_pattern = self.target_branch_pattern[3:]
4515 target_pattern = self.target_branch_pattern[3:]
4516 else:
4516 else:
4517 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4517 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4518 target_branch_regex = re.compile(target_pattern)
4518 target_branch_regex = re.compile(target_pattern)
4519 target_branch_match = bool(target_branch_regex.search(target_branch))
4519 target_branch_match = bool(target_branch_regex.search(target_branch))
4520
4520
4521 branch_matches = source_branch_match and target_branch_match
4521 branch_matches = source_branch_match and target_branch_match
4522
4522
4523 files_matches = True
4523 files_matches = True
4524 if self.file_pattern != '*':
4524 if self.file_pattern != '*':
4525 files_matches = False
4525 files_matches = False
4526 if self.file_pattern.startswith('re:'):
4526 if self.file_pattern.startswith('re:'):
4527 file_pattern = self.file_pattern[3:]
4527 file_pattern = self.file_pattern[3:]
4528 else:
4528 else:
4529 file_pattern = glob2re(self.file_pattern)
4529 file_pattern = glob2re(self.file_pattern)
4530 file_regex = re.compile(file_pattern)
4530 file_regex = re.compile(file_pattern)
4531 for filename in files_changed:
4531 for filename in files_changed:
4532 if file_regex.search(filename):
4532 if file_regex.search(filename):
4533 files_matches = True
4533 files_matches = True
4534 break
4534 break
4535
4535
4536 return branch_matches and files_matches
4536 return branch_matches and files_matches
4537
4537
4538 @property
4538 @property
4539 def review_users(self):
4539 def review_users(self):
4540 """ Returns the users which this rule applies to """
4540 """ Returns the users which this rule applies to """
4541
4541
4542 users = collections.OrderedDict()
4542 users = collections.OrderedDict()
4543
4543
4544 for rule_user in self.rule_users:
4544 for rule_user in self.rule_users:
4545 if rule_user.user.active:
4545 if rule_user.user.active:
4546 if rule_user.user not in users:
4546 if rule_user.user not in users:
4547 users[rule_user.user.username] = {
4547 users[rule_user.user.username] = {
4548 'user': rule_user.user,
4548 'user': rule_user.user,
4549 'source': 'user',
4549 'source': 'user',
4550 'source_data': {},
4550 'source_data': {},
4551 'data': rule_user.rule_data()
4551 'data': rule_user.rule_data()
4552 }
4552 }
4553
4553
4554 for rule_user_group in self.rule_user_groups:
4554 for rule_user_group in self.rule_user_groups:
4555 source_data = {
4555 source_data = {
4556 'user_group_id': rule_user_group.users_group.users_group_id,
4556 'user_group_id': rule_user_group.users_group.users_group_id,
4557 'name': rule_user_group.users_group.users_group_name,
4557 'name': rule_user_group.users_group.users_group_name,
4558 'members': len(rule_user_group.users_group.members)
4558 'members': len(rule_user_group.users_group.members)
4559 }
4559 }
4560 for member in rule_user_group.users_group.members:
4560 for member in rule_user_group.users_group.members:
4561 if member.user.active:
4561 if member.user.active:
4562 key = member.user.username
4562 key = member.user.username
4563 if key in users:
4563 if key in users:
4564 # skip this member as we have him already
4564 # skip this member as we have him already
4565 # this prevents from override the "first" matched
4565 # this prevents from override the "first" matched
4566 # users with duplicates in multiple groups
4566 # users with duplicates in multiple groups
4567 continue
4567 continue
4568
4568
4569 users[key] = {
4569 users[key] = {
4570 'user': member.user,
4570 'user': member.user,
4571 'source': 'user_group',
4571 'source': 'user_group',
4572 'source_data': source_data,
4572 'source_data': source_data,
4573 'data': rule_user_group.rule_data()
4573 'data': rule_user_group.rule_data()
4574 }
4574 }
4575
4575
4576 return users
4576 return users
4577
4577
4578 def user_group_vote_rule(self, user_id):
4578 def user_group_vote_rule(self, user_id):
4579
4579
4580 rules = []
4580 rules = []
4581 if not self.rule_user_groups:
4581 if not self.rule_user_groups:
4582 return rules
4582 return rules
4583
4583
4584 for user_group in self.rule_user_groups:
4584 for user_group in self.rule_user_groups:
4585 user_group_members = [x.user_id for x in user_group.users_group.members]
4585 user_group_members = [x.user_id for x in user_group.users_group.members]
4586 if user_id in user_group_members:
4586 if user_id in user_group_members:
4587 rules.append(user_group)
4587 rules.append(user_group)
4588 return rules
4588 return rules
4589
4589
4590 def __repr__(self):
4590 def __repr__(self):
4591 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4591 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4592 self.repo_review_rule_id, self.repo)
4592 self.repo_review_rule_id, self.repo)
4593
4593
4594
4594
4595 class ScheduleEntry(Base, BaseModel):
4595 class ScheduleEntry(Base, BaseModel):
4596 __tablename__ = 'schedule_entries'
4596 __tablename__ = 'schedule_entries'
4597 __table_args__ = (
4597 __table_args__ = (
4598 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4598 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4599 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4599 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4600 base_table_args,
4600 base_table_args,
4601 )
4601 )
4602
4602
4603 schedule_types = ['crontab', 'timedelta', 'integer']
4603 schedule_types = ['crontab', 'timedelta', 'integer']
4604 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4604 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4605
4605
4606 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4606 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4607 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4607 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4608 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4608 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4609
4609
4610 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4610 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4611 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4611 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4612
4612
4613 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4613 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4614 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4614 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4615
4615
4616 # task
4616 # task
4617 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4617 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4618 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4618 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4619 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4619 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4620 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4620 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4621
4621
4622 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4622 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4623 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4623 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4624
4624
4625 @hybrid_property
4625 @hybrid_property
4626 def schedule_type(self):
4626 def schedule_type(self):
4627 return self._schedule_type
4627 return self._schedule_type
4628
4628
4629 @schedule_type.setter
4629 @schedule_type.setter
4630 def schedule_type(self, val):
4630 def schedule_type(self, val):
4631 if val not in self.schedule_types:
4631 if val not in self.schedule_types:
4632 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4632 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4633 val, self.schedule_type))
4633 val, self.schedule_type))
4634
4634
4635 self._schedule_type = val
4635 self._schedule_type = val
4636
4636
4637 @classmethod
4637 @classmethod
4638 def get_uid(cls, obj):
4638 def get_uid(cls, obj):
4639 args = obj.task_args
4639 args = obj.task_args
4640 kwargs = obj.task_kwargs
4640 kwargs = obj.task_kwargs
4641 if isinstance(args, JsonRaw):
4641 if isinstance(args, JsonRaw):
4642 try:
4642 try:
4643 args = json.loads(args)
4643 args = json.loads(args)
4644 except ValueError:
4644 except ValueError:
4645 args = tuple()
4645 args = tuple()
4646
4646
4647 if isinstance(kwargs, JsonRaw):
4647 if isinstance(kwargs, JsonRaw):
4648 try:
4648 try:
4649 kwargs = json.loads(kwargs)
4649 kwargs = json.loads(kwargs)
4650 except ValueError:
4650 except ValueError:
4651 kwargs = dict()
4651 kwargs = dict()
4652
4652
4653 dot_notation = obj.task_dot_notation
4653 dot_notation = obj.task_dot_notation
4654 val = '.'.join(map(safe_str, [
4654 val = '.'.join(map(safe_str, [
4655 sorted(dot_notation), args, sorted(kwargs.items())]))
4655 sorted(dot_notation), args, sorted(kwargs.items())]))
4656 return hashlib.sha1(val).hexdigest()
4656 return hashlib.sha1(val).hexdigest()
4657
4657
4658 @classmethod
4658 @classmethod
4659 def get_by_schedule_name(cls, schedule_name):
4659 def get_by_schedule_name(cls, schedule_name):
4660 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4660 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4661
4661
4662 @classmethod
4662 @classmethod
4663 def get_by_schedule_id(cls, schedule_id):
4663 def get_by_schedule_id(cls, schedule_id):
4664 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4664 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4665
4665
4666 @property
4666 @property
4667 def task(self):
4667 def task(self):
4668 return self.task_dot_notation
4668 return self.task_dot_notation
4669
4669
4670 @property
4670 @property
4671 def schedule(self):
4671 def schedule(self):
4672 from rhodecode.lib.celerylib.utils import raw_2_schedule
4672 from rhodecode.lib.celerylib.utils import raw_2_schedule
4673 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4673 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4674 return schedule
4674 return schedule
4675
4675
4676 @property
4676 @property
4677 def args(self):
4677 def args(self):
4678 try:
4678 try:
4679 return list(self.task_args or [])
4679 return list(self.task_args or [])
4680 except ValueError:
4680 except ValueError:
4681 return list()
4681 return list()
4682
4682
4683 @property
4683 @property
4684 def kwargs(self):
4684 def kwargs(self):
4685 try:
4685 try:
4686 return dict(self.task_kwargs or {})
4686 return dict(self.task_kwargs or {})
4687 except ValueError:
4687 except ValueError:
4688 return dict()
4688 return dict()
4689
4689
4690 def _as_raw(self, val):
4690 def _as_raw(self, val):
4691 if hasattr(val, 'de_coerce'):
4691 if hasattr(val, 'de_coerce'):
4692 val = val.de_coerce()
4692 val = val.de_coerce()
4693 if val:
4693 if val:
4694 val = json.dumps(val)
4694 val = json.dumps(val)
4695
4695
4696 return val
4696 return val
4697
4697
4698 @property
4698 @property
4699 def schedule_definition_raw(self):
4699 def schedule_definition_raw(self):
4700 return self._as_raw(self.schedule_definition)
4700 return self._as_raw(self.schedule_definition)
4701
4701
4702 @property
4702 @property
4703 def args_raw(self):
4703 def args_raw(self):
4704 return self._as_raw(self.task_args)
4704 return self._as_raw(self.task_args)
4705
4705
4706 @property
4706 @property
4707 def kwargs_raw(self):
4707 def kwargs_raw(self):
4708 return self._as_raw(self.task_kwargs)
4708 return self._as_raw(self.task_kwargs)
4709
4709
4710 def __repr__(self):
4710 def __repr__(self):
4711 return '<DB:ScheduleEntry({}:{})>'.format(
4711 return '<DB:ScheduleEntry({}:{})>'.format(
4712 self.schedule_entry_id, self.schedule_name)
4712 self.schedule_entry_id, self.schedule_name)
4713
4713
4714
4714
4715 @event.listens_for(ScheduleEntry, 'before_update')
4715 @event.listens_for(ScheduleEntry, 'before_update')
4716 def update_task_uid(mapper, connection, target):
4716 def update_task_uid(mapper, connection, target):
4717 target.task_uid = ScheduleEntry.get_uid(target)
4717 target.task_uid = ScheduleEntry.get_uid(target)
4718
4718
4719
4719
4720 @event.listens_for(ScheduleEntry, 'before_insert')
4720 @event.listens_for(ScheduleEntry, 'before_insert')
4721 def set_task_uid(mapper, connection, target):
4721 def set_task_uid(mapper, connection, target):
4722 target.task_uid = ScheduleEntry.get_uid(target)
4722 target.task_uid = ScheduleEntry.get_uid(target)
4723
4723
4724
4724
4725 class _BaseBranchPerms(BaseModel):
4725 class _BaseBranchPerms(BaseModel):
4726 @classmethod
4726 @classmethod
4727 def compute_hash(cls, value):
4727 def compute_hash(cls, value):
4728 return sha1_safe(value)
4728 return sha1_safe(value)
4729
4729
4730 @hybrid_property
4730 @hybrid_property
4731 def branch_pattern(self):
4731 def branch_pattern(self):
4732 return self._branch_pattern or '*'
4732 return self._branch_pattern or '*'
4733
4733
4734 @hybrid_property
4734 @hybrid_property
4735 def branch_hash(self):
4735 def branch_hash(self):
4736 return self._branch_hash
4736 return self._branch_hash
4737
4737
4738 def _validate_glob(self, value):
4738 def _validate_glob(self, value):
4739 re.compile('^' + glob2re(value) + '$')
4739 re.compile('^' + glob2re(value) + '$')
4740
4740
4741 @branch_pattern.setter
4741 @branch_pattern.setter
4742 def branch_pattern(self, value):
4742 def branch_pattern(self, value):
4743 self._validate_glob(value)
4743 self._validate_glob(value)
4744 self._branch_pattern = value or '*'
4744 self._branch_pattern = value or '*'
4745 # set the Hash when setting the branch pattern
4745 # set the Hash when setting the branch pattern
4746 self._branch_hash = self.compute_hash(self._branch_pattern)
4746 self._branch_hash = self.compute_hash(self._branch_pattern)
4747
4747
4748 def matches(self, branch):
4748 def matches(self, branch):
4749 """
4749 """
4750 Check if this the branch matches entry
4750 Check if this the branch matches entry
4751
4751
4752 :param branch: branch name for the commit
4752 :param branch: branch name for the commit
4753 """
4753 """
4754
4754
4755 branch = branch or ''
4755 branch = branch or ''
4756
4756
4757 branch_matches = True
4757 branch_matches = True
4758 if branch:
4758 if branch:
4759 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4759 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4760 branch_matches = bool(branch_regex.search(branch))
4760 branch_matches = bool(branch_regex.search(branch))
4761
4761
4762 return branch_matches
4762 return branch_matches
4763
4763
4764
4764
4765 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4765 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4766 __tablename__ = 'user_to_repo_branch_permissions'
4766 __tablename__ = 'user_to_repo_branch_permissions'
4767 __table_args__ = (
4767 __table_args__ = (
4768 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4768 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4769 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4769 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4770 )
4770 )
4771
4771
4772 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4772 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4773
4773
4774 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4774 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4775 repo = relationship('Repository', backref='user_branch_perms')
4775 repo = relationship('Repository', backref='user_branch_perms')
4776
4776
4777 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4777 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4778 permission = relationship('Permission')
4778 permission = relationship('Permission')
4779
4779
4780 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4780 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4781 user_repo_to_perm = relationship('UserRepoToPerm')
4781 user_repo_to_perm = relationship('UserRepoToPerm')
4782
4782
4783 rule_order = Column('rule_order', Integer(), nullable=False)
4783 rule_order = Column('rule_order', Integer(), nullable=False)
4784 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4784 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4785 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4785 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4786
4786
4787 def __unicode__(self):
4787 def __unicode__(self):
4788 return u'<UserBranchPermission(%s => %r)>' % (
4788 return u'<UserBranchPermission(%s => %r)>' % (
4789 self.user_repo_to_perm, self.branch_pattern)
4789 self.user_repo_to_perm, self.branch_pattern)
4790
4790
4791
4791
4792 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4792 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4793 __tablename__ = 'user_group_to_repo_branch_permissions'
4793 __tablename__ = 'user_group_to_repo_branch_permissions'
4794 __table_args__ = (
4794 __table_args__ = (
4795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4797 )
4797 )
4798
4798
4799 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4799 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4800
4800
4801 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4801 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4802 repo = relationship('Repository', backref='user_group_branch_perms')
4802 repo = relationship('Repository', backref='user_group_branch_perms')
4803
4803
4804 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4804 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4805 permission = relationship('Permission')
4805 permission = relationship('Permission')
4806
4806
4807 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)
4807 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)
4808 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4808 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4809
4809
4810 rule_order = Column('rule_order', Integer(), nullable=False)
4810 rule_order = Column('rule_order', Integer(), nullable=False)
4811 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4811 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4812 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4812 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4813
4813
4814 def __unicode__(self):
4814 def __unicode__(self):
4815 return u'<UserBranchPermission(%s => %r)>' % (
4815 return u'<UserBranchPermission(%s => %r)>' % (
4816 self.user_group_repo_to_perm, self.branch_pattern)
4816 self.user_group_repo_to_perm, self.branch_pattern)
4817
4817
4818
4818
4819 class UserBookmark(Base, BaseModel):
4819 class UserBookmark(Base, BaseModel):
4820 __tablename__ = 'user_bookmarks'
4820 __tablename__ = 'user_bookmarks'
4821 __table_args__ = (
4821 __table_args__ = (
4822 UniqueConstraint('user_id', 'bookmark_repo_id'),
4822 UniqueConstraint('user_id', 'bookmark_repo_id'),
4823 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4823 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4824 UniqueConstraint('user_id', 'bookmark_position'),
4824 UniqueConstraint('user_id', 'bookmark_position'),
4825 base_table_args
4825 base_table_args
4826 )
4826 )
4827
4827
4828 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4828 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4829 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4829 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4830 position = Column("bookmark_position", Integer(), nullable=False)
4830 position = Column("bookmark_position", Integer(), nullable=False)
4831 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4831 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4832 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4832 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4833 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4833 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4834
4834
4835 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
4835 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
4836 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
4836 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
4837
4837
4838 user = relationship("User")
4838 user = relationship("User")
4839
4839
4840 repository = relationship("Repository")
4840 repository = relationship("Repository")
4841 repository_group = relationship("RepoGroup")
4841 repository_group = relationship("RepoGroup")
4842
4842
4843 @classmethod
4843 @classmethod
4844 def get_by_position_for_user(cls, position, user_id):
4844 def get_by_position_for_user(cls, position, user_id):
4845 return cls.query() \
4845 return cls.query() \
4846 .filter(UserBookmark.user_id == user_id) \
4846 .filter(UserBookmark.user_id == user_id) \
4847 .filter(UserBookmark.position == position).scalar()
4847 .filter(UserBookmark.position == position).scalar()
4848
4848
4849 @classmethod
4849 @classmethod
4850 def get_bookmarks_for_user(cls, user_id):
4850 def get_bookmarks_for_user(cls, user_id):
4851 return cls.query() \
4851 return cls.query() \
4852 .filter(UserBookmark.user_id == user_id) \
4852 .filter(UserBookmark.user_id == user_id) \
4853 .options(joinedload(UserBookmark.repository)) \
4853 .options(joinedload(UserBookmark.repository)) \
4854 .options(joinedload(UserBookmark.repository_group)) \
4854 .options(joinedload(UserBookmark.repository_group)) \
4855 .order_by(UserBookmark.position.asc()) \
4855 .order_by(UserBookmark.position.asc()) \
4856 .all()
4856 .all()
4857
4857
4858 def __unicode__(self):
4858 def __unicode__(self):
4859 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
4859 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
4860
4860
4861
4861
4862 class FileStore(Base, BaseModel):
4862 class FileStore(Base, BaseModel):
4863 __tablename__ = 'file_store'
4863 __tablename__ = 'file_store'
4864 __table_args__ = (
4864 __table_args__ = (
4865 base_table_args
4865 base_table_args
4866 )
4866 )
4867
4867
4868 file_store_id = Column('file_store_id', Integer(), primary_key=True)
4868 file_store_id = Column('file_store_id', Integer(), primary_key=True)
4869 file_uid = Column('file_uid', String(1024), nullable=False)
4869 file_uid = Column('file_uid', String(1024), nullable=False)
4870 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
4870 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
4871 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
4871 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
4872 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
4872 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
4873
4873
4874 # sha256 hash
4874 # sha256 hash
4875 file_hash = Column('file_hash', String(512), nullable=False)
4875 file_hash = Column('file_hash', String(512), nullable=False)
4876 file_size = Column('file_size', Integer(), nullable=False)
4876 file_size = Column('file_size', Integer(), nullable=False)
4877
4877
4878 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4878 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4879 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
4879 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
4880 accessed_count = Column('accessed_count', Integer(), default=0)
4880 accessed_count = Column('accessed_count', Integer(), default=0)
4881
4881
4882 enabled = Column('enabled', Boolean(), nullable=False, default=True)
4882 enabled = Column('enabled', Boolean(), nullable=False, default=True)
4883
4883
4884 # if repo/repo_group reference is set, check for permissions
4884 # if repo/repo_group reference is set, check for permissions
4885 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
4885 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
4886
4886
4887 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
4887 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
4888 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
4888 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
4889
4889
4890 # scope limited to user, which requester have access to
4890 # scope limited to user, which requester have access to
4891 scope_user_id = Column(
4891 scope_user_id = Column(
4892 'scope_user_id', Integer(), ForeignKey('users.user_id'),
4892 'scope_user_id', Integer(), ForeignKey('users.user_id'),
4893 nullable=True, unique=None, default=None)
4893 nullable=True, unique=None, default=None)
4894 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
4894 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
4895
4895
4896 # scope limited to user group, which requester have access to
4896 # scope limited to user group, which requester have access to
4897 scope_user_group_id = Column(
4897 scope_user_group_id = Column(
4898 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
4898 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
4899 nullable=True, unique=None, default=None)
4899 nullable=True, unique=None, default=None)
4900 user_group = relationship('UserGroup', lazy='joined')
4900 user_group = relationship('UserGroup', lazy='joined')
4901
4901
4902 # scope limited to repo, which requester have access to
4902 # scope limited to repo, which requester have access to
4903 scope_repo_id = Column(
4903 scope_repo_id = Column(
4904 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4904 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4905 nullable=True, unique=None, default=None)
4905 nullable=True, unique=None, default=None)
4906 repo = relationship('Repository', lazy='joined')
4906 repo = relationship('Repository', lazy='joined')
4907
4907
4908 # scope limited to repo group, which requester have access to
4908 # scope limited to repo group, which requester have access to
4909 scope_repo_group_id = Column(
4909 scope_repo_group_id = Column(
4910 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
4910 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
4911 nullable=True, unique=None, default=None)
4911 nullable=True, unique=None, default=None)
4912 repo_group = relationship('RepoGroup', lazy='joined')
4912 repo_group = relationship('RepoGroup', lazy='joined')
4913
4913
4914 @classmethod
4914 @classmethod
4915 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
4915 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
4916 file_description='', enabled=True, check_acl=True,
4916 file_description='', enabled=True, check_acl=True,
4917 user_id=None, scope_repo_id=None, scope_repo_group_id=None):
4917 user_id=None, scope_repo_id=None, scope_repo_group_id=None):
4918
4918
4919 store_entry = FileStore()
4919 store_entry = FileStore()
4920 store_entry.file_uid = file_uid
4920 store_entry.file_uid = file_uid
4921 store_entry.file_display_name = file_display_name
4921 store_entry.file_display_name = file_display_name
4922 store_entry.file_org_name = filename
4922 store_entry.file_org_name = filename
4923 store_entry.file_size = file_size
4923 store_entry.file_size = file_size
4924 store_entry.file_hash = file_hash
4924 store_entry.file_hash = file_hash
4925 store_entry.file_description = file_description
4925 store_entry.file_description = file_description
4926
4926
4927 store_entry.check_acl = check_acl
4927 store_entry.check_acl = check_acl
4928 store_entry.enabled = enabled
4928 store_entry.enabled = enabled
4929
4929
4930 store_entry.user_id = user_id
4930 store_entry.user_id = user_id
4931 store_entry.scope_repo_id = scope_repo_id
4931 store_entry.scope_repo_id = scope_repo_id
4932 store_entry.scope_repo_group_id = scope_repo_group_id
4932 store_entry.scope_repo_group_id = scope_repo_group_id
4933 return store_entry
4933 return store_entry
4934
4934
4935 @classmethod
4935 @classmethod
4936 def bump_access_counter(cls, file_uid, commit=True):
4936 def bump_access_counter(cls, file_uid, commit=True):
4937 FileStore().query()\
4937 FileStore().query()\
4938 .filter(FileStore.file_uid == file_uid)\
4938 .filter(FileStore.file_uid == file_uid)\
4939 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
4939 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
4940 FileStore.accessed_on: datetime.datetime.now()})
4940 FileStore.accessed_on: datetime.datetime.now()})
4941 if commit:
4941 if commit:
4942 Session().commit()
4942 Session().commit()
4943
4943
4944 def __repr__(self):
4944 def __repr__(self):
4945 return '<FileStore({})>'.format(self.file_store_id)
4945 return '<FileStore({})>'.format(self.file_store_id)
4946
4946
4947
4947
4948 class DbMigrateVersion(Base, BaseModel):
4948 class DbMigrateVersion(Base, BaseModel):
4949 __tablename__ = 'db_migrate_version'
4949 __tablename__ = 'db_migrate_version'
4950 __table_args__ = (
4950 __table_args__ = (
4951 base_table_args,
4951 base_table_args,
4952 )
4952 )
4953
4953
4954 repository_id = Column('repository_id', String(250), primary_key=True)
4954 repository_id = Column('repository_id', String(250), primary_key=True)
4955 repository_path = Column('repository_path', Text)
4955 repository_path = Column('repository_path', Text)
4956 version = Column('version', Integer)
4956 version = Column('version', Integer)
4957
4957
4958 @classmethod
4958 @classmethod
4959 def set_version(cls, version):
4959 def set_version(cls, version):
4960 """
4960 """
4961 Helper for forcing a different version, usually for debugging purposes via ishell.
4961 Helper for forcing a different version, usually for debugging purposes via ishell.
4962 """
4962 """
4963 ver = DbMigrateVersion.query().first()
4963 ver = DbMigrateVersion.query().first()
4964 ver.version = version
4964 ver.version = version
4965 Session().commit()
4965 Session().commit()
4966
4966
4967
4967
4968 class DbSession(Base, BaseModel):
4968 class DbSession(Base, BaseModel):
4969 __tablename__ = 'db_session'
4969 __tablename__ = 'db_session'
4970 __table_args__ = (
4970 __table_args__ = (
4971 base_table_args,
4971 base_table_args,
4972 )
4972 )
4973
4973
4974 def __repr__(self):
4974 def __repr__(self):
4975 return '<DB:DbSession({})>'.format(self.id)
4975 return '<DB:DbSession({})>'.format(self.id)
4976
4976
4977 id = Column('id', Integer())
4977 id = Column('id', Integer())
4978 namespace = Column('namespace', String(255), primary_key=True)
4978 namespace = Column('namespace', String(255), primary_key=True)
4979 accessed = Column('accessed', DateTime, nullable=False)
4979 accessed = Column('accessed', DateTime, nullable=False)
4980 created = Column('created', DateTime, nullable=False)
4980 created = Column('created', DateTime, nullable=False)
4981 data = Column('data', PickleType, nullable=False)
4981 data = Column('data', PickleType, nullable=False)
@@ -1,698 +1,733 b''
1 // navigation.less
1 // navigation.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5 // HEADER NAVIGATION
5 // HEADER NAVIGATION
6
6
7 .horizontal-list {
7 .horizontal-list {
8 float: right;
8 float: right;
9 display: block;
9 display: block;
10 margin: 0;
10 margin: 0;
11 padding: 0;
11 padding: 0;
12 -webkit-padding-start: 0;
12 -webkit-padding-start: 0;
13 text-align: left;
13 text-align: left;
14 font-size: @navigation-fontsize;
14 font-size: @navigation-fontsize;
15 color: @grey6;
15 color: @grey6;
16 z-index:10;
16 z-index:10;
17
17
18 li {
18 li {
19 line-height: 1em;
19 line-height: 1em;
20 list-style-type: none;
20 list-style-type: none;
21
21
22 a {
22 a {
23 padding: 0 .5em;
23 padding: 0 .5em;
24
24
25 &.menu_link_notifications {
25 &.menu_link_notifications {
26 .pill(7px,@rcblue);
26 .pill(7px,@rcblue);
27 display: inline;
27 display: inline;
28 margin: 0 7px 0 .7em;
28 margin: 0 7px 0 .7em;
29 font-size: @basefontsize;
29 font-size: @basefontsize;
30 color: white;
30 color: white;
31
31
32 &.empty {
32 &.empty {
33 background-color: @grey4;
33 background-color: @grey4;
34 }
34 }
35
35
36 &:hover {
36 &:hover {
37 background-color: @rcdarkblue;
37 background-color: @rcdarkblue;
38 }
38 }
39 }
39 }
40 }
40 }
41 .pill_container {
41 .pill_container {
42 margin: 1.25em 0px 0px 0px;
42 margin: 1.25em 0px 0px 0px;
43 float: right;
43 float: right;
44 }
44 }
45
45
46 &#quick_login_li {
46 &#quick_login_li {
47 &:hover {
47 &:hover {
48 color: @grey5;
48 color: @grey5;
49 }
49 }
50
50
51 a.menu_link_notifications {
51 a.menu_link_notifications {
52 color: white;
52 color: white;
53 }
53 }
54
54
55 .user {
55 .user {
56 padding-bottom: 10px;
56 padding-bottom: 10px;
57 }
57 }
58
58
59 &.open {
59 &.open {
60 .user {
60 .user {
61 border-bottom: 5px solid @rcblue;
61 border-bottom: 5px solid @rcblue;
62 }
62 }
63 }
63 }
64 }
64 }
65
65
66 &:before { content: none; }
66 &:before { content: none; }
67
67
68 &:last-child {
68 &:last-child {
69 .menulabel {
69 .menulabel {
70 padding-right: 0;
70 padding-right: 0;
71 border-right: none;
71 border-right: none;
72
72
73 .show_more {
73 .show_more {
74 padding-right: 0;
74 padding-right: 0;
75 }
75 }
76 }
76 }
77
77
78 &> a {
78 &> a {
79 border-bottom: none;
79 border-bottom: none;
80 }
80 }
81 }
81 }
82
82
83 &.active {
83 &.active {
84 border-bottom: 5px solid @rcblue;
84 border-bottom: 5px solid @rcblue;
85 }
85 }
86
86
87 &.open {
87 &.open {
88
88
89 a {
89 a {
90 color: white;
90 color: white;
91 }
91 }
92 }
92 }
93
93
94 &:focus {
94 &:focus {
95 outline: none;
95 outline: none;
96 }
96 }
97
97
98 ul li {
98 ul li {
99 display: block;
99 display: block;
100
100
101 &:last-child> a {
101 &:last-child> a {
102 border-bottom: none;
102 border-bottom: none;
103 }
103 }
104
104
105 ul li:last-child a {
105 ul li:last-child a {
106 /* we don't expect more then 3 levels of submenu and the third
106 /* we don't expect more then 3 levels of submenu and the third
107 level can have different html structure */
107 level can have different html structure */
108 border-bottom: none;
108 border-bottom: none;
109 }
109 }
110 }
110 }
111 }
111 }
112
112
113 > li {
113 > li {
114 float: left;
114 float: left;
115 display: block;
115 display: block;
116 padding: 0;
116 padding: 0;
117
117
118 > a,
118 > a,
119 &.has_select2 a {
119 &.has_select2 a {
120 display: block;
120 display: block;
121 padding: 10px 0 2px;
121 padding: 10px 0 2px;
122 }
122 }
123
123
124 .menulabel {
124 .menulabel {
125 padding: 0 .5em;
125 padding: 0 .5em;
126 line-height: 1em;
126 line-height: 1em;
127 // for this specifically we do not use a variable
127 // for this specifically we do not use a variable
128 border-right: 1px solid @grey4;
128 border-right: 1px solid @grey4;
129 }
129 }
130
130
131 .pr_notifications {
131 .pr_notifications {
132 padding-left: .5em;
132 padding-left: .5em;
133 }
133 }
134
134
135 .pr_notifications + .menulabel {
135 .pr_notifications + .menulabel {
136 display:inline;
136 display:inline;
137 padding-left: 0;
137 padding-left: 0;
138 }
138 }
139
139
140 &:hover,
140 &:hover,
141 &.open,
141 &.open,
142 &.active {
142 &.active {
143 a {
143 a {
144 color: @grey1;
144 color: @grey1;
145 }
145 }
146 }
146 }
147 }
147 }
148
148
149 pre {
149 pre {
150 margin: 0;
150 margin: 0;
151 padding: 0;
151 padding: 0;
152 }
152 }
153
153
154 .select2-container,
154 .select2-container,
155 .menulink.childs {
155 .menulink.childs {
156 position: relative;
156 position: relative;
157 }
157 }
158
158
159 #quick_login {
159 #quick_login {
160
160
161 li a {
161 li a {
162 padding: .5em 0;
162 padding: .5em 0;
163 border-bottom: none;
163 border-bottom: none;
164 color: @grey2;
164 color: @grey2;
165
165
166 &:hover { color: @grey1; }
166 &:hover { color: @grey1; }
167 }
167 }
168 }
168 }
169
169
170 #quick_login_link {
170 #quick_login_link {
171 display: inline-block;
171 display: inline-block;
172
172
173 .gravatar {
173 .gravatar {
174 border: 1px solid @grey5;
174 border: 1px solid @grey5;
175 }
175 }
176
176
177 .gravatar-login {
177 .gravatar-login {
178 height: 20px;
178 height: 20px;
179 width: 20px;
179 width: 20px;
180 margin: -8px 0;
180 margin: -8px 0;
181 padding: 0;
181 padding: 0;
182 }
182 }
183
183
184 &:hover .user {
184 &:hover .user {
185 color: @grey6;
185 color: @grey6;
186 }
186 }
187 }
187 }
188 }
188 }
189 .header .horizontal-list {
189 .header .horizontal-list {
190
190
191 li {
191 li {
192
192
193 &#quick_login_li {
193 &#quick_login_li {
194 padding-left: .5em;
194 padding-left: .5em;
195
195
196 &:hover #quick_login_link {
196 &:hover #quick_login_link {
197 color: inherit;
197 color: inherit;
198 }
198 }
199
199
200 .menu_link_user {
200 .menu_link_user {
201 padding: 0 2px;
201 padding: 0 2px;
202 }
202 }
203 }
203 }
204 list-style-type: none;
204 list-style-type: none;
205 }
205 }
206
206
207 > li {
207 > li {
208
208
209 a {
209 a {
210 padding: 18px 0 12px 0;
210 padding: 18px 0 12px 0;
211 color: @nav-grey;
211 color: @nav-grey;
212
212
213 &.menu_link_notifications {
213 &.menu_link_notifications {
214 padding: 1px 8px;
214 padding: 1px 8px;
215 }
215 }
216 }
216 }
217
217
218 &:hover,
218 &:hover,
219 &.open,
219 &.open,
220 &.active {
220 &.active {
221 .pill_container a {
221 .pill_container a {
222 // don't select text for the pill container, it has it' own
222 // don't select text for the pill container, it has it' own
223 // hover behaviour
223 // hover behaviour
224 color: @nav-grey;
224 color: @nav-grey;
225 }
225 }
226 }
226 }
227
227
228 &:hover,
228 &:hover,
229 &.open,
229 &.open,
230 &.active {
230 &.active {
231 a {
231 a {
232 color: @grey6;
232 color: @grey6;
233 }
233 }
234 }
234 }
235
235
236 .select2-dropdown-open a {
236 .select2-dropdown-open a {
237 color: @grey6;
237 color: @grey6;
238 }
238 }
239
239
240 .repo-switcher {
240 .repo-switcher {
241 padding-left: 0;
241 padding-left: 0;
242
242
243 .menulabel {
243 .menulabel {
244 padding-left: 0;
244 padding-left: 0;
245 }
245 }
246 }
246 }
247 }
247 }
248
248
249 li ul li {
249 li ul li {
250 background-color:@grey2;
250 background-color:@grey2;
251
251
252 a {
252 a {
253 padding: .5em 0;
253 padding: .5em 0;
254 border-bottom: @border-thickness solid @border-default-color;
254 border-bottom: @border-thickness solid @border-default-color;
255 color: @grey6;
255 color: @grey6;
256 }
256 }
257
257
258 &:last-child a, &.last a{
258 &:last-child a, &.last a{
259 border-bottom: none;
259 border-bottom: none;
260 }
260 }
261
261
262 &:hover {
262 &:hover {
263 background-color: @grey3;
263 background-color: @grey3;
264 }
264 }
265 }
265 }
266
266
267 .submenu {
267 .submenu {
268 margin-top: 5px;
268 margin-top: 5px;
269 }
269 }
270 }
270 }
271
271
272 // SUBMENUS
272 // SUBMENUS
273 .navigation .submenu {
273 .navigation .submenu {
274 display: none;
274 display: none;
275 }
275 }
276
276
277 .navigation li.open {
277 .navigation li.open {
278 .submenu {
278 .submenu {
279 display: block;
279 display: block;
280 }
280 }
281 }
281 }
282
282
283 .navigation li:last-child .submenu {
283 .navigation li:last-child .submenu {
284 right: -20px;
284 right: -20px;
285 left: auto;
285 left: auto;
286 }
286 }
287
287
288 .submenu {
288 .submenu {
289 position: absolute;
289 position: absolute;
290 top: 100%;
290 top: 100%;
291 left: 0;
291 left: 0;
292 min-width: 150px;
292 min-width: 150px;
293 margin: 6px 0 0;
293 margin: 6px 0 0;
294 padding: 0;
294 padding: 0;
295 text-align: left;
295 text-align: left;
296 font-family: @text-light;
296 font-family: @text-light;
297 border-radius: @border-radius;
297 border-radius: @border-radius;
298 z-index: 20;
298 z-index: 20;
299
299
300 li {
300 li {
301 display: block;
301 display: block;
302 margin: 0;
302 margin: 0;
303 padding: 0 .5em;
303 padding: 0 .5em;
304 line-height: 1em;
304 line-height: 1em;
305 color: @grey3;
305 color: @grey3;
306 background-color: @grey6;
306 background-color: @grey6;
307 list-style-type: none;
307 list-style-type: none;
308
308
309 a {
309 a {
310 display: block;
310 display: block;
311 width: 100%;
311 width: 100%;
312 padding: .5em 0;
312 padding: .5em 0;
313 border-right: none;
313 border-right: none;
314 border-bottom: @border-thickness solid white;
314 border-bottom: @border-thickness solid white;
315 color: @grey3;
315 color: @grey3;
316 }
316 }
317
317
318 ul {
318 ul {
319 display: none;
319 display: none;
320 position: absolute;
320 position: absolute;
321 top: 0;
321 top: 0;
322 right: 100%;
322 right: 100%;
323 padding: 0;
323 padding: 0;
324 z-index: 30;
324 z-index: 30;
325 }
325 }
326 &:hover {
326 &:hover {
327 background-color: @grey5;
327 background-color: @grey5;
328 -webkit-transition: background .3s;
328 -webkit-transition: background .3s;
329 -moz-transition: background .3s;
329 -moz-transition: background .3s;
330 -o-transition: background .3s;
330 -o-transition: background .3s;
331 transition: background .3s;
331 transition: background .3s;
332
332
333 ul {
333 ul {
334 display: block;
334 display: block;
335 }
335 }
336 }
336 }
337 }
337 }
338
338
339 }
339 }
340
340
341
341
342
342
343
343
344 // repo dropdown
344 // repo dropdown
345 .quick_repo_menu {
345 .quick_repo_menu {
346 width: 15px;
346 width: 15px;
347 text-align: center;
347 text-align: center;
348 position: relative;
348 position: relative;
349 cursor: pointer;
349 cursor: pointer;
350
350
351 div {
351 div {
352 overflow: visible !important;
352 overflow: visible !important;
353 }
353 }
354
354
355 &.sorting {
355 &.sorting {
356 cursor: auto;
356 cursor: auto;
357 }
357 }
358
358
359 &:hover {
359 &:hover {
360 .menu_items_container {
360 .menu_items_container {
361 position: absolute;
361 position: absolute;
362 display: block;
362 display: block;
363 }
363 }
364 .menu_items {
364 .menu_items {
365 display: block;
365 display: block;
366 }
366 }
367 }
367 }
368
368
369 i {
369 i {
370 margin: 0;
370 margin: 0;
371 color: @grey4;
371 color: @grey4;
372 }
372 }
373
373
374 .menu_items_container {
374 .menu_items_container {
375 position: absolute;
375 position: absolute;
376 top: 0;
376 top: 0;
377 left: 100%;
377 left: 100%;
378 margin: 0;
378 margin: 0;
379 padding: 0;
379 padding: 0;
380 list-style: none;
380 list-style: none;
381 background-color: @grey6;
381 background-color: @grey6;
382 z-index: 999;
382 z-index: 999;
383 text-align: left;
383 text-align: left;
384
384
385 a {
385 a {
386 color: @grey2;
386 color: @grey2;
387 }
387 }
388
388
389 ul.menu_items {
389 ul.menu_items {
390 margin: 0;
390 margin: 0;
391 padding: 0;
391 padding: 0;
392 }
392 }
393
393
394 li {
394 li {
395 margin: 0;
395 margin: 0;
396 padding: 0;
396 padding: 0;
397 line-height: 1em;
397 line-height: 1em;
398 list-style-type: none;
398 list-style-type: none;
399
399
400 a {
400 a {
401 display: block;
401 display: block;
402 height: 16px;
402 height: 16px;
403 padding: 8px; //must add up to td height (28px)
403 padding: 8px; //must add up to td height (28px)
404 width: 120px; // set width
404 width: 120px; // set width
405
405
406 &:hover {
406 &:hover {
407 background-color: @grey5;
407 background-color: @grey5;
408 -webkit-transition: background .3s;
408 -webkit-transition: background .3s;
409 -moz-transition: background .3s;
409 -moz-transition: background .3s;
410 -o-transition: background .3s;
410 -o-transition: background .3s;
411 transition: background .3s;
411 transition: background .3s;
412 }
412 }
413 }
413 }
414 }
414 }
415 }
415 }
416 }
416 }
417
417
418 // Header Repository Switcher
418 // Header Repository Switcher
419 // Select2 Dropdown
419 // Select2 Dropdown
420 #select2-drop.select2-drop.repo-switcher-dropdown {
420 #select2-drop.select2-drop.repo-switcher-dropdown {
421 width: auto !important;
421 width: auto !important;
422 margin-top: 5px;
422 margin-top: 5px;
423 padding: 1em 0;
423 padding: 1em 0;
424 text-align: left;
424 text-align: left;
425 .border-radius-bottom(@border-radius);
425 .border-radius-bottom(@border-radius);
426 border-color: transparent;
426 border-color: transparent;
427 color: @grey6;
427 color: @grey6;
428 background-color: @grey2;
428 background-color: @grey2;
429
429
430 input {
430 input {
431 min-width: 90%;
431 min-width: 90%;
432 }
432 }
433
433
434 ul.select2-result-sub {
434 ul.select2-result-sub {
435
435
436 li {
436 li {
437 line-height: 1em;
437 line-height: 1em;
438
438
439 &:hover,
439 &:hover,
440 &.select2-highlighted {
440 &.select2-highlighted {
441 background-color: @grey3;
441 background-color: @grey3;
442 }
442 }
443 }
443 }
444
444
445 &:before { content: none; }
445 &:before { content: none; }
446 }
446 }
447
447
448 ul.select2-results {
448 ul.select2-results {
449 min-width: 200px;
449 min-width: 200px;
450 margin: 0;
450 margin: 0;
451 padding: 0;
451 padding: 0;
452 list-style-type: none;
452 list-style-type: none;
453 overflow-x: visible;
453 overflow-x: visible;
454 overflow-y: scroll;
454 overflow-y: scroll;
455
455
456 li {
456 li {
457 padding: 0 8px;
457 padding: 0 8px;
458 line-height: 1em;
458 line-height: 1em;
459 color: @grey6;
459 color: @grey6;
460
460
461 &>.select2-result-label {
461 &>.select2-result-label {
462 padding: 8px 0;
462 padding: 8px 0;
463 border-bottom: @border-thickness solid @grey3;
463 border-bottom: @border-thickness solid @grey3;
464 white-space: nowrap;
464 white-space: nowrap;
465 color: @grey5;
465 color: @grey5;
466 cursor: pointer;
466 cursor: pointer;
467 }
467 }
468
468
469 &.select2-result-with-children {
469 &.select2-result-with-children {
470 margin: 0;
470 margin: 0;
471 padding: 0;
471 padding: 0;
472 }
472 }
473
473
474 &.select2-result-unselectable > .select2-result-label {
474 &.select2-result-unselectable > .select2-result-label {
475 margin: 0 8px;
475 margin: 0 8px;
476 }
476 }
477
477
478 }
478 }
479 }
479 }
480
480
481 ul.select2-result-sub {
481 ul.select2-result-sub {
482 margin: 0;
482 margin: 0;
483 padding: 0;
483 padding: 0;
484
484
485 li {
485 li {
486 display: block;
486 display: block;
487 margin: 0;
487 margin: 0;
488 border-right: none;
488 border-right: none;
489 line-height: 1em;
489 line-height: 1em;
490 font-family: @text-light;
490 font-family: @text-light;
491 color: @grey2;
491 color: @grey2;
492 list-style-type: none;
492 list-style-type: none;
493
493
494 &:hover {
494 &:hover {
495 background-color: @grey3;
495 background-color: @grey3;
496 }
496 }
497 }
497 }
498 }
498 }
499 }
499 }
500
500
501
501
502 #context-bar {
502 #context-bar {
503 display: block;
503 display: block;
504 margin: 0 auto;
504 margin: 0 auto;
505 padding: 0 @header-padding;
505 padding: 0 @header-padding;
506 background-color: @grey6;
506 background-color: @grey6;
507 border-bottom: @border-thickness solid @grey5;
507 border-bottom: @border-thickness solid @grey5;
508
508
509 .clear {
509 .clear {
510 clear: both;
510 clear: both;
511 }
511 }
512 }
512 }
513
513
514 ul#context-pages {
514 ul#context-pages {
515 li {
515 li {
516 line-height: 1em;
516 line-height: 1em;
517 list-style-type: none;
517 list-style-type: none;
518
518
519 a {
519 a {
520 color: @grey3;
520 color: @grey3;
521 }
521 }
522
522
523 &.active {
523 &.active {
524 // special case, non-variable color
524 // special case, non-variable color
525 border-bottom: 4px solid @nav-grey;
525 border-bottom: 4px solid @nav-grey;
526
526
527 a {
527 a {
528 color: @grey1;
528 color: @grey1;
529 }
529 }
530 }
530 }
531 }
531 }
532 }
532 }
533
533
534 // PAGINATION
534 // PAGINATION
535
535
536 .pagination {
536 .pagination {
537 border: @border-thickness solid @rcblue;
537 border: @border-thickness solid @rcblue;
538 color: @rcblue;
538 color: @rcblue;
539
539
540 .current {
540 .current {
541 color: @grey4;
541 color: @grey4;
542 }
542 }
543 }
543 }
544
544
545 .dataTables_processing {
545 .dataTables_processing {
546 text-align: center;
546 text-align: center;
547 font-size: 1.1em;
547 font-size: 1.1em;
548 position: relative;
548 position: relative;
549 top: 95px;
549 top: 95px;
550 }
550 }
551
551
552 .dataTables_paginate, .pagination-wh {
552 .dataTables_paginate, .pagination-wh {
553 text-align: left;
553 text-align: left;
554 display: inline-block;
554 display: inline-block;
555 border-left: 1px solid @rcblue;
555 border-left: 1px solid @rcblue;
556 float: none;
556 float: none;
557 overflow: hidden;
557 overflow: hidden;
558
558
559 .paginate_button, .pager_curpage,
559 .paginate_button, .pager_curpage,
560 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
560 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
561 display: inline-block;
561 display: inline-block;
562 padding: @menupadding/4 @menupadding;
562 padding: @menupadding/4 @menupadding;
563 border: 1px solid @rcblue;
563 border: 1px solid @rcblue;
564 border-left: 0;
564 border-left: 0;
565 color: @rcblue;
565 color: @rcblue;
566 cursor: pointer;
566 cursor: pointer;
567 float: left;
567 float: left;
568 }
568 }
569
569
570 .pager_curpage, .pager_dotdot,
570 .pager_curpage, .pager_dotdot,
571 .paginate_button.current, .paginate_button.disabled,
571 .paginate_button.current, .paginate_button.disabled,
572 .disabled {
572 .disabled {
573 color: @grey3;
573 color: @grey3;
574 cursor: default;
574 cursor: default;
575 }
575 }
576
576
577 .ellipsis {
577 .ellipsis {
578 display: inline-block;
578 display: inline-block;
579 text-align: left;
579 text-align: left;
580 padding: @menupadding/4 @menupadding;
580 padding: @menupadding/4 @menupadding;
581 border: 1px solid @rcblue;
581 border: 1px solid @rcblue;
582 border-left: 0;
582 border-left: 0;
583 float: left;
583 float: left;
584 }
584 }
585 }
585 }
586
586
587 // SIDEBAR
587 // SIDEBAR
588
588
589 .sidebar {
589 .sidebar {
590 .block-left;
590 .block-left;
591 clear: left;
591 clear: left;
592 max-width: @sidebar-width;
592 max-width: @sidebar-width;
593 margin-right: @sidebarpadding;
593 margin-right: @sidebarpadding;
594 padding-right: @sidebarpadding;
594 padding-right: @sidebarpadding;
595 font-family: @text-regular;
595 font-family: @text-regular;
596 color: @grey1;
596 color: @grey1;
597
597
598 &#graph_nodes {
598 &#graph_nodes {
599 clear:both;
599 clear:both;
600 width: auto;
600 width: auto;
601 margin-left: -100px;
601 margin-left: -100px;
602 padding: 0;
602 padding: 0;
603 border: none;
603 border: none;
604 }
604 }
605
605
606 .nav-pills {
606 .nav-pills {
607 margin: 0;
607 margin: 0;
608 }
608 }
609
609
610 .nav {
610 .nav {
611 list-style: none;
611 list-style: none;
612 padding: 0;
612 padding: 0;
613
613
614 li {
614 li {
615 padding-bottom: @menupadding;
615 padding-bottom: @menupadding;
616 line-height: 1em;
616 line-height: 1em;
617 color: @grey4;
617 color: @grey4;
618 list-style-type: none;
618 list-style-type: none;
619
619
620 &.active a {
620 &.active a {
621 color: @grey2;
621 color: @grey2;
622 }
622 }
623
623
624 a {
624 a {
625 color: @grey4;
625 color: @grey4;
626 }
626 }
627 }
627 }
628
628
629 }
629 }
630 }
630 }
631
631
632 .main_filter_help_box {
632 .main_filter_help_box {
633 padding: 7px 7px;
633 padding: 7px 7px;
634 border-top: 1px solid @grey4;
634 border-top: 1px solid @grey4;
635 border-right: 1px solid @grey4;
635 border-right: 1px solid @grey4;
636 border-bottom: 1px solid @grey4;
636 border-bottom: 1px solid @grey4;
637 display: inline-block;
637 display: inline-block;
638 vertical-align: top;
638 vertical-align: top;
639 background: inherit;
639 background: inherit;
640 position: absolute;
640 position: absolute;
641 right: 8px;
641 right: 8px;
642 top: 9px;
642 top: 9px;
643 }
643 }
644
644
645 .main_filter_input_box {
645 .main_filter_input_box {
646 display: inline-block;
646 display: inline-block;
647
648 .searchItems {
649 display:flex;
650 background: #666666;
651 padding: 0px;
652
653 a {
654 border: none !important;
655 }
656 }
657
658 .searchTag {
659 line-height: 28px;
660 padding: 0px 4px;
661
662 .tag {
663 color: @nav-grey;
664 border-color: @nav-grey;
665 }
666 }
667
668 .searchTagFilter {
669 background-color: @grey3 !important;
670 }
671
672 .searchTagHelp {
673 background-color: @grey2 !important;
674 }
675 .searchTagHelp:hover {
676 background-color: @grey2 !important;
677 }
678 .searchTagInput {
679 background-color: @grey3 !important;
680 }
647 }
681 }
648
682
649 .main_filter_box {
683 .main_filter_box {
650 margin: 9px 0 0 0;
684 margin: 9px 0 0 0;
651 }
685 }
652
686
653 #main_filter_help {
687 #main_filter_help {
654 background: @grey3;
688 background: @grey3;
655 border: 1px solid black;
689 border: 1px solid black;
656 position: absolute;
690 position: absolute;
657 white-space: pre;
691 white-space: pre;
658 z-index: 9999;
692 z-index: 9999;
659 color: @nav-grey;
693 color: @nav-grey;
660 margin: 1px 7px;
694 margin: 1px 7px;
661 padding: 0 10px;
695 padding: 0 10px;
662 }
696 }
663
697
664 .main_filter_input {
698 .main_filter_input {
665 padding: 5px;
699 padding: 5px;
666 min-width: 260px;
700 min-width: 260px;
667 color: @nav-grey;
701 color: @nav-grey;
668 background: @grey3;
702 background: @grey3;
669 min-height: 18px;
703 min-height: 18px;
670
704 border:none;
705 border-radius: 0;
671
706
672 &:active {
707 &:active {
673 color: @grey2 !important;
708 color: @grey2 !important;
674 background: white !important;
709 background: white !important;
675 }
710 }
676 &:focus {
711 &:focus {
677 color: @grey2 !important;
712 color: @grey2 !important;
678 background: white !important;
713 background: white !important;
679 }
714 }
680 }
715 }
681
716
682
717
683
718
684 .main_filter_input::placeholder {
719 .main_filter_input::placeholder {
685 color: @nav-grey;
720 color: @nav-grey;
686 opacity: 1;
721 opacity: 1;
687 }
722 }
688
723
689 .notice-box {
724 .notice-box {
690 display:block !important;
725 display:block !important;
691 padding: 9px 0 !important;
726 padding: 9px 0 !important;
692 }
727 }
693
728
694 .menulabel-notice {
729 .menulabel-notice {
695 border: 1px solid @color5;
730 border: 1px solid @color5;
696 padding:7px 10px;
731 padding:7px 10px;
697 color: @color5;
732 color: @color5;
698 }
733 }
@@ -1,936 +1,948 b''
1 /**
1 /**
2 * Ajax Autocomplete for jQuery, version dev
2 * Ajax Autocomplete for jQuery, version dev
3 * RhodeCode additions
3 * RhodeCode additions
4 * (c) 2014 Tomas Kirda
4 * (c) 2014 Tomas Kirda
5 * (c) 2014 Marcin Kuzminski
5 * (c) 2014 Marcin Kuzminski
6 *
6 *
7 * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
7 * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
8 * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
8 * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
9 */
9 */
10 // Expose plugin as an AMD module if AMD loader is present:
10 // Expose plugin as an AMD module if AMD loader is present:
11 (function (factory) {
11 (function (factory) {
12 'use strict';
12 'use strict';
13 if (typeof define === 'function' && define.amd) {
13 if (typeof define === 'function' && define.amd) {
14 // AMD. Register as an anonymous module.
14 // AMD. Register as an anonymous module.
15 define(['jquery'], factory);
15 define(['jquery'], factory);
16 } else if (typeof exports === 'object' && typeof require === 'function') {
16 } else if (typeof exports === 'object' && typeof require === 'function') {
17 // Browserify
17 // Browserify
18 factory(require('jquery'));
18 factory(require('jquery'));
19 } else {
19 } else {
20 // Browser globals
20 // Browser globals
21 factory(jQuery);
21 factory(jQuery);
22 }
22 }
23 }(function ($) {
23 }(function ($) {
24 'use strict';
24 'use strict';
25
25
26 var
26 var
27 utils = (function () {
27 utils = (function () {
28 return {
28 return {
29 escapeRegExChars: function (value) {
29 escapeRegExChars: function (value) {
30 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
30 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
31 },
31 },
32 createNode: function (containerClass) {
32 createNode: function (containerClass) {
33 var div = document.createElement('div');
33 var div = document.createElement('div');
34 div.className = containerClass;
34 div.className = containerClass;
35 div.style.position = 'absolute';
35 div.style.position = 'absolute';
36 div.style.display = 'none';
36 div.style.display = 'none';
37 return div;
37 return div;
38 }
38 }
39 };
39 };
40 }()),
40 }()),
41
41
42 keys = {
42 keys = {
43 ESC: 27,
43 ESC: 27,
44 TAB: 9,
44 TAB: 9,
45 RETURN: 13,
45 RETURN: 13,
46 LEFT: 37,
46 LEFT: 37,
47 UP: 38,
47 UP: 38,
48 RIGHT: 39,
48 RIGHT: 39,
49 DOWN: 40
49 DOWN: 40
50 };
50 };
51
51
52 function Autocomplete(el, options) {
52 function Autocomplete(el, options) {
53 var noop = function () { },
53 var noop = function () { },
54 that = this,
54 that = this,
55 defaults = {
55 defaults = {
56 ajaxSettings: {},
56 ajaxSettings: {},
57 autoSelectFirst: false,
57 autoSelectFirst: false,
58 appendTo: document.body,
58 appendTo: document.body,
59 serviceUrl: null,
59 serviceUrl: null,
60 lookup: null,
60 lookup: null,
61 width: 'auto',
61 width: 'auto',
62 minChars: 1,
62 minChars: 1,
63 maxHeight: 300,
63 maxHeight: 300,
64 deferRequestBy: 0,
64 deferRequestBy: 0,
65 params: {},
65 params: {},
66 formatResult: Autocomplete.formatResult,
66 formatResult: Autocomplete.formatResult,
67 lookupFilter: Autocomplete.lookupFilter,
67 lookupFilter: Autocomplete.lookupFilter,
68 delimiter: null,
68 delimiter: null,
69 zIndex: 9999,
69 zIndex: 9999,
70 type: 'GET',
70 type: 'GET',
71 noCache: false,
71 noCache: false,
72 onSelect: noop,
72 onSelect: noop,
73 onSearchStart: noop,
73 onSearchStart: noop,
74 onSearchComplete: noop,
74 onSearchComplete: noop,
75 onSearchError: noop,
75 onSearchError: noop,
76 containerClass: 'autocomplete-suggestions',
76 containerClass: 'autocomplete-suggestions',
77 tabDisabled: false,
77 tabDisabled: false,
78 dataType: 'text',
78 dataType: 'text',
79 currentRequest: null,
79 currentRequest: null,
80 triggerSelectOnValidInput: false,
80 triggerSelectOnValidInput: false,
81 preventBadQueries: true,
81 preventBadQueries: true,
82 paramName: 'query',
82 paramName: 'query',
83 transformResult: function (response) {
83 transformResult: function (response) {
84 return typeof response === 'string' ? $.parseJSON(response) : response;
84 return typeof response === 'string' ? $.parseJSON(response) : response;
85 },
85 },
86 showNoSuggestionNotice: false,
86 showNoSuggestionNotice: false,
87 noSuggestionNotice: _gettext('No results'),
87 noSuggestionNotice: _gettext('No results'),
88 orientation: 'bottom',
88 orientation: 'bottom',
89 forceFixPosition: false,
89 forceFixPosition: false,
90 replaceOnArrowKey: true
90 replaceOnArrowKey: true
91 };
91 };
92
92
93 // Shared variables:
93 // Shared variables:
94 that.element = el;
94 that.element = el;
95 that.el = $(el);
95 that.el = $(el);
96 that.suggestions = [];
96 that.suggestions = [];
97 that.badQueries = [];
97 that.badQueries = [];
98 that.selectedIndex = -1;
98 that.selectedIndex = -1;
99 that.currentValue = that.element.value;
99 that.currentValue = that.element.value;
100 that.intervalId = 0;
100 that.intervalId = 0;
101 that.cachedResponse = {};
101 that.cachedResponse = {};
102 that.onChangeInterval = null;
102 that.onChangeInterval = null;
103 that.onChange = null;
103 that.onChange = null;
104 that.isLocal = false;
104 that.isLocal = false;
105 that.suggestionsContainer = null;
105 that.suggestionsContainer = null;
106 that.noSuggestionsContainer = null;
106 that.noSuggestionsContainer = null;
107 that.options = $.extend({}, defaults, options);
107 that.options = $.extend({}, defaults, options);
108 that.classes = {
108 that.classes = {
109 selected: 'autocomplete-selected',
109 selected: 'autocomplete-selected',
110 suggestion: 'autocomplete-suggestion'
110 suggestion: 'autocomplete-suggestion'
111 };
111 };
112 that.hint = null;
112 that.hint = null;
113 that.hintValue = '';
113 that.hintValue = '';
114 that.selection = null;
114 that.selection = null;
115
115
116 // Initialize and set options:
116 // Initialize and set options:
117 that.initialize();
117 that.initialize();
118 that.setOptions(options);
118 that.setOptions(options);
119 }
119 }
120
120
121 Autocomplete.utils = utils;
121 Autocomplete.utils = utils;
122
122
123 $.Autocomplete = Autocomplete;
123 $.Autocomplete = Autocomplete;
124
124
125 Autocomplete.formatResult = function (suggestion, currentValue) {
125 Autocomplete.formatResult = function (suggestion, currentValue) {
126 var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
126 var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
127 return suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
127 return suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
128 };
128 };
129 Autocomplete.lookupFilter = function (suggestion, originalQuery, queryLowerCase) {
129 Autocomplete.lookupFilter = function (suggestion, originalQuery, queryLowerCase) {
130 return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
130 return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
131 };
131 };
132
132
133 Autocomplete.prototype = {
133 Autocomplete.prototype = {
134
134
135 killerFn: null,
135 killerFn: null,
136
136
137 initialize: function () {
137 initialize: function () {
138 var that = this,
138 var that = this,
139 suggestionSelector = '.' + that.classes.suggestion,
139 suggestionSelector = '.' + that.classes.suggestion,
140 selected = that.classes.selected,
140 selected = that.classes.selected,
141 options = that.options,
141 options = that.options,
142 container;
142 container;
143
143
144 // Remove autocomplete attribute to prevent native suggestions:
144 // Remove autocomplete attribute to prevent native suggestions:
145 that.element.setAttribute('autocomplete', 'off');
145 that.element.setAttribute('autocomplete', 'off');
146
146
147 that.killerFn = function (e) {
147 that.killerFn = function (e) {
148 if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
148 if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
149 that.killSuggestions();
149 that.killSuggestions();
150 that.disableKillerFn();
150 that.disableKillerFn();
151 }
151 }
152 };
152 };
153
153
154 // html() deals with many types: htmlString or Element or Array or jQuery
154 // html() deals with many types: htmlString or Element or Array or jQuery
155 that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
155 that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
156 .html(this.options.noSuggestionNotice).get(0);
156 .html(this.options.noSuggestionNotice).get(0);
157
157
158 that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
158 that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
159
159
160 container = $(that.suggestionsContainer);
160 container = $(that.suggestionsContainer);
161
161
162 container.appendTo(options.appendTo);
162 container.appendTo(options.appendTo);
163
163
164 // Only set width if it was provided:
164 // Only set width if it was provided:
165 if (options.width !== 'auto') {
165 if (options.width !== 'auto') {
166 container.width(options.width);
166 container.width(options.width);
167 }
167 }
168
168
169 // Listen for mouse over event on suggestions list:
169 // Listen for mouse over event on suggestions list:
170 container.on('mouseover.autocomplete', suggestionSelector, function () {
170 container.on('mouseover.autocomplete', suggestionSelector, function () {
171 that.activate($(this).data('index'));
171 that.activate($(this).data('index'));
172 });
172 });
173
173
174 // Deselect active element when mouse leaves suggestions container:
174 // Deselect active element when mouse leaves suggestions container:
175 container.on('mouseout.autocomplete', function () {
175 container.on('mouseout.autocomplete', function () {
176 that.selectedIndex = -1;
176 that.selectedIndex = -1;
177 container.children('.' + selected).removeClass(selected);
177 container.children('.' + selected).removeClass(selected);
178 });
178 });
179
179
180 // Listen for click event on suggestions list:
180 // Listen for click event on suggestions list:
181 container.on('click.autocomplete', suggestionSelector, function () {
181 container.on('click.autocomplete', suggestionSelector, function () {
182 that.select($(this).data('index'));
182 that.select($(this).data('index'));
183 });
183 });
184
184
185 that.fixPositionCapture = function () {
185 that.fixPositionCapture = function () {
186 if (that.visible) {
186 if (that.visible) {
187 that.fixPosition();
187 that.fixPosition();
188 }
188 }
189 };
189 };
190
190
191 $(window).on('resize.autocomplete', that.fixPositionCapture);
191 $(window).on('resize.autocomplete', that.fixPositionCapture);
192
192
193 that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
193 that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
194 that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
194 that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
195 that.el.on('blur.autocomplete', function () { that.onBlur(); });
195 that.el.on('blur.autocomplete', function () { that.onBlur(); });
196 that.el.on('focus.autocomplete', function () { that.onFocus(); });
196 that.el.on('focus.autocomplete', function () { that.onFocus(); });
197 that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
197 that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
198 },
198 },
199
199
200 onFocus: function () {
200 onFocus: function () {
201 var that = this;
201 var that = this;
202 that.fixPosition();
202 that.fixPosition();
203 if (that.options.minChars <= that.el.val().length) {
203 if (that.options.minChars <= that.el.val().length) {
204 that.onValueChange();
204 that.onValueChange();
205 }
205 }
206 },
206 },
207
207
208 onBlur: function () {
208 onBlur: function () {
209 this.enableKillerFn();
209 this.enableKillerFn();
210 },
210 },
211
211
212 setOptions: function (suppliedOptions) {
212 setOptions: function (suppliedOptions) {
213 var that = this,
213 var that = this,
214 options = that.options;
214 options = that.options;
215
215
216 $.extend(options, suppliedOptions);
216 $.extend(options, suppliedOptions);
217
217
218 that.isLocal = $.isArray(options.lookup);
218 that.isLocal = $.isArray(options.lookup);
219
219
220 if (that.isLocal) {
220 if (that.isLocal) {
221 options.lookup = that.verifySuggestionsFormat(options.lookup);
221 options.lookup = that.verifySuggestionsFormat(options.lookup);
222 }
222 }
223
223
224 options.orientation = that.validateOrientation(options.orientation, 'bottom');
224 options.orientation = that.validateOrientation(options.orientation, 'bottom');
225
225
226 // Adjust height, width and z-index:
226 // Adjust height, width and z-index:
227 $(that.suggestionsContainer).css({
227 $(that.suggestionsContainer).css({
228 'max-height': options.maxHeight + 'px',
228 'max-height': options.maxHeight + 'px',
229 'width': options.width + 'px',
229 'width': options.width + 'px',
230 'z-index': options.zIndex
230 'z-index': options.zIndex
231 });
231 });
232 },
232 },
233
233
234 clearCache: function () {
234 clearCache: function () {
235 this.cachedResponse = {};
235 this.cachedResponse = {};
236 this.badQueries = [];
236 this.badQueries = [];
237 },
237 },
238
238
239 clear: function () {
239 clear: function () {
240 this.clearCache();
240 this.clearCache();
241 this.currentValue = '';
241 this.currentValue = '';
242 this.suggestions = [];
242 this.suggestions = [];
243 },
243 },
244
244
245 disable: function () {
245 disable: function () {
246 var that = this;
246 var that = this;
247 that.disabled = true;
247 that.disabled = true;
248 if (that.currentRequest) {
248 if (that.currentRequest) {
249 that.currentRequest.abort();
249 that.currentRequest.abort();
250 }
250 }
251 },
251 },
252
252
253 enable: function () {
253 enable: function () {
254 this.disabled = false;
254 this.disabled = false;
255 },
255 },
256
256
257 fixPosition: function () {
257 fixPosition: function () {
258 // Use only when container has already its content
258 // Use only when container has already its content
259
259
260 var that = this,
260 var that = this,
261 $container = $(that.suggestionsContainer),
261 $container = $(that.suggestionsContainer),
262 containerParent = $container.parent().get(0);
262 containerParent = $container.parent().get(0);
263 // Fix position automatically when appended to body.
263 // Fix position automatically when appended to body.
264 // In other cases force parameter must be given.
264 // In other cases force parameter must be given.
265 if (containerParent !== document.body && !that.options.forceFixPosition)
265 if (containerParent !== document.body && !that.options.forceFixPosition)
266 return;
266 return;
267
267
268 // Choose orientation
268 // Choose orientation
269 var orientation = that.options.orientation,
269 var orientation = that.options.orientation,
270 containerHeight = $container.outerHeight(),
270 containerHeight = $container.outerHeight(),
271 height = that.el.outerHeight(),
271 height = that.el.outerHeight(),
272 offset = that.el.offset(),
272 offset = that.el.offset(),
273 styles = { 'top': offset.top, 'left': offset.left };
273 styles = { 'top': offset.top, 'left': offset.left };
274
274
275 if (orientation == 'auto') {
275 if (orientation == 'auto') {
276 var viewPortHeight = $(window).height(),
276 var viewPortHeight = $(window).height(),
277 scrollTop = $(window).scrollTop(),
277 scrollTop = $(window).scrollTop(),
278 topOverflow = -scrollTop + offset.top - containerHeight,
278 topOverflow = -scrollTop + offset.top - containerHeight,
279 bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
279 bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
280
280
281 orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow)
281 orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow)
282 ? 'top'
282 ? 'top'
283 : 'bottom';
283 : 'bottom';
284 }
284 }
285
285
286 if (orientation === 'top') {
286 if (orientation === 'top') {
287 styles.top += -containerHeight;
287 styles.top += -containerHeight;
288 } else {
288 } else {
289 styles.top += height;
289 styles.top += height;
290 }
290 }
291
291
292 // If container is not positioned to body,
292 // If container is not positioned to body,
293 // correct its position using offset parent offset
293 // correct its position using offset parent offset
294 if(containerParent !== document.body) {
294 if(containerParent !== document.body) {
295 var opacity = $container.css('opacity'),
295 var opacity = $container.css('opacity'),
296 parentOffsetDiff;
296 parentOffsetDiff;
297
297
298 if (!that.visible){
298 if (!that.visible){
299 $container.css('opacity', 0).show();
299 $container.css('opacity', 0).show();
300 }
300 }
301
301
302 parentOffsetDiff = $container.offsetParent().offset();
302 parentOffsetDiff = $container.offsetParent().offset();
303 styles.top -= parentOffsetDiff.top;
303 styles.top -= parentOffsetDiff.top;
304 styles.left -= parentOffsetDiff.left;
304 styles.left -= parentOffsetDiff.left;
305
305
306 if (!that.visible){
306 if (!that.visible){
307 $container.css('opacity', opacity).hide();
307 $container.css('opacity', opacity).hide();
308 }
308 }
309 }
309 }
310
310
311 // -2px to account for suggestions border.
311 // -2px to account for suggestions border.
312 if (that.options.width === 'auto') {
312 if (that.options.width === 'auto') {
313 styles.width = (that.el.outerWidth() - 2) + 'px';
313 styles.width = (that.el.outerWidth() - 2) + 'px';
314 }
314 }
315
315
316 $container.css(styles);
316 $container.css(styles);
317 },
317 },
318
318
319 enableKillerFn: function () {
319 enableKillerFn: function () {
320 var that = this;
320 var that = this;
321 $(document).on('click.autocomplete', that.killerFn);
321 $(document).on('click.autocomplete', that.killerFn);
322 },
322 },
323
323
324 disableKillerFn: function () {
324 disableKillerFn: function () {
325 var that = this;
325 var that = this;
326 $(document).off('click.autocomplete', that.killerFn);
326 $(document).off('click.autocomplete', that.killerFn);
327 },
327 },
328
328
329 killSuggestions: function () {
329 killSuggestions: function () {
330 var that = this;
330 var that = this;
331 that.stopKillSuggestions();
331 that.stopKillSuggestions();
332 that.intervalId = window.setInterval(function () {
332 that.intervalId = window.setInterval(function () {
333 that.hide();
333 that.hide();
334 that.stopKillSuggestions();
334 that.stopKillSuggestions();
335 }, 50);
335 }, 50);
336 },
336 },
337
337
338 stopKillSuggestions: function () {
338 stopKillSuggestions: function () {
339 window.clearInterval(this.intervalId);
339 window.clearInterval(this.intervalId);
340 },
340 },
341
341
342 isCursorAtEnd: function () {
342 isCursorAtEnd: function () {
343 var that = this,
343 var that = this,
344 valLength = that.el.val().length,
344 valLength = that.el.val().length,
345 selectionStart = that.element.selectionStart,
345 selectionStart = that.element.selectionStart,
346 range;
346 range;
347
347
348 if (typeof selectionStart === 'number') {
348 if (typeof selectionStart === 'number') {
349 return selectionStart === valLength;
349 return selectionStart === valLength;
350 }
350 }
351 if (document.selection) {
351 if (document.selection) {
352 range = document.selection.createRange();
352 range = document.selection.createRange();
353 range.moveStart('character', -valLength);
353 range.moveStart('character', -valLength);
354 return valLength === range.text.length;
354 return valLength === range.text.length;
355 }
355 }
356 return true;
356 return true;
357 },
357 },
358
358
359 onKeyPress: function (e) {
359 onKeyPress: function (e) {
360 var that = this;
360 var that = this;
361
361
362 // If suggestions are hidden and user presses arrow down, display suggestions:
362 // If suggestions are hidden and user presses arrow down, display suggestions:
363 if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
363 if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
364 that.suggest();
364 that.suggest();
365 return;
365 return;
366 }
366 }
367
367
368 if (that.disabled || !that.visible) {
368 if (that.disabled || !that.visible) {
369 return;
369 return;
370 }
370 }
371
371
372 switch (e.which) {
372 switch (e.which) {
373 case keys.ESC:
373 case keys.ESC:
374 that.el.val(that.currentValue);
374 that.el.val(that.currentValue);
375 that.hide();
375 that.hide();
376 break;
376 break;
377 case keys.RIGHT:
377 case keys.RIGHT:
378 if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
378 if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
379 that.selectHint();
379 that.selectHint();
380 break;
380 break;
381 }
381 }
382 return;
382 return;
383 case keys.TAB:
383 case keys.TAB:
384 if (that.hint && that.options.onHint) {
384 if (that.hint && that.options.onHint) {
385 that.selectHint();
385 that.selectHint();
386 return;
386 return;
387 }
387 }
388 // Fall through to RETURN
388 // Fall through to RETURN
389 case keys.RETURN:
389 case keys.RETURN:
390 if (that.selectedIndex === -1) {
390 if (that.selectedIndex === -1) {
391 that.hide();
391 that.hide();
392 return;
392 return;
393 }
393 }
394 that.select(that.selectedIndex);
394 that.select(that.selectedIndex);
395 if (e.which === keys.TAB && that.options.tabDisabled === false) {
395 if (e.which === keys.TAB && that.options.tabDisabled === false) {
396 return;
396 return;
397 }
397 }
398 break;
398 break;
399 case keys.UP:
399 case keys.UP:
400 that.moveUp();
400 that.moveUp();
401 break;
401 break;
402 case keys.DOWN:
402 case keys.DOWN:
403 that.moveDown();
403 that.moveDown();
404 break;
404 break;
405 default:
405 default:
406 return;
406 return;
407 }
407 }
408
408
409 // Cancel event if function did not return:
409 // Cancel event if function did not return:
410 e.stopImmediatePropagation();
410 e.stopImmediatePropagation();
411 e.preventDefault();
411 e.preventDefault();
412 },
412 },
413
413
414 onKeyUp: function (e) {
414 onKeyUp: function (e) {
415 var that = this;
415 var that = this;
416
416
417 if (that.disabled) {
417 if (that.disabled) {
418 return;
418 return;
419 }
419 }
420
420
421 switch (e.which) {
421 switch (e.which) {
422 case keys.UP:
422 case keys.UP:
423 case keys.DOWN:
423 case keys.DOWN:
424 return;
424 return;
425 }
425 }
426
426
427 clearInterval(that.onChangeInterval);
427 clearInterval(that.onChangeInterval);
428
428
429 if (that.currentValue !== that.el.val()) {
429 if (that.currentValue !== that.el.val()) {
430 that.findBestHint();
430 that.findBestHint();
431 if (that.options.deferRequestBy > 0) {
431 if (that.options.deferRequestBy > 0) {
432 // Defer lookup in case when value changes very quickly:
432 // Defer lookup in case when value changes very quickly:
433 that.onChangeInterval = setInterval(function () {
433 that.onChangeInterval = setInterval(function () {
434 that.onValueChange();
434 that.onValueChange();
435 }, that.options.deferRequestBy);
435 }, that.options.deferRequestBy);
436 } else {
436 } else {
437 that.onValueChange();
437 that.onValueChange();
438 }
438 }
439 }
439 }
440 },
440 },
441
441
442 onValueChange: function () {
442 onValueChange: function () {
443 var that = this,
443 var that = this,
444 options = that.options,
444 options = that.options,
445 value = that.el.val(),
445 value = that.el.val(),
446 query = that.getQuery(value),
446 query = that.getQuery(value),
447 index;
447 index;
448
448
449 if (that.selection && that.currentValue !== query) {
449 if (that.selection && that.currentValue !== query) {
450 that.selection = null;
450 that.selection = null;
451 (options.onInvalidateSelection || $.noop).call(that.element);
451 (options.onInvalidateSelection || $.noop).call(that.element);
452 }
452 }
453
453
454 clearInterval(that.onChangeInterval);
454 clearInterval(that.onChangeInterval);
455 that.currentValue = value;
455 that.currentValue = value;
456 that.selectedIndex = -1;
456 that.selectedIndex = -1;
457
457
458 // Check existing suggestion for the match before proceeding:
458 // Check existing suggestion for the match before proceeding:
459 if (options.triggerSelectOnValidInput) {
459 if (options.triggerSelectOnValidInput) {
460 index = that.findSuggestionIndex(query);
460 index = that.findSuggestionIndex(query);
461 if (index !== -1) {
461 if (index !== -1) {
462 that.select(index);
462 that.select(index);
463 return;
463 return;
464 }
464 }
465 }
465 }
466
466
467 if (query.length < options.minChars) {
467 if (query.length < options.minChars) {
468 that.hide();
468 that.hide();
469 } else {
469 } else {
470 that.getSuggestions(query);
470 that.getSuggestions(query);
471 }
471 }
472 },
472 },
473
473
474 findSuggestionIndex: function (query) {
474 findSuggestionIndex: function (query) {
475 var that = this,
475 var that = this,
476 index = -1,
476 index = -1,
477 queryLowerCase = query.toLowerCase();
477 queryLowerCase = query.toLowerCase();
478
478
479 $.each(that.suggestions, function (i, suggestion) {
479 $.each(that.suggestions, function (i, suggestion) {
480 if (suggestion.value.toLowerCase() === queryLowerCase) {
480 if (suggestion.value.toLowerCase() === queryLowerCase) {
481 index = i;
481 index = i;
482 return false;
482 return false;
483 }
483 }
484 });
484 });
485
485
486 return index;
486 return index;
487 },
487 },
488
488
489 getQuery: function (value) {
489 getQuery: function (value) {
490 var delimiter = this.options.delimiter,
490 var delimiter = this.options.delimiter,
491 parts;
491 parts;
492
492
493 if (!delimiter) {
493 if (!delimiter) {
494 return value;
494 return value;
495 }
495 }
496 parts = value.split(delimiter);
496 parts = value.split(delimiter);
497 return $.trim(parts[parts.length - 1]);
497 return $.trim(parts[parts.length - 1]);
498 },
498 },
499
499
500 getSuggestionsLocal: function (query) {
500 getSuggestionsLocal: function (query) {
501 var that = this,
501 var that = this,
502 options = that.options,
502 options = that.options,
503 queryLowerCase = query.toLowerCase(),
503 queryLowerCase = query.toLowerCase(),
504 data;
504 data;
505
505
506 // re-pack the data as it was comming from AJAX
506 // re-pack the data as it was comming from AJAX
507 data = {
507 data = {
508 suggestions: data
508 suggestions: data
509 };
509 };
510 return data;
510 return data;
511 },
511 },
512
512
513 getSuggestions: function (query) {
513 getSuggestions: function (query) {
514 var response,
514 var response,
515 that = this,
515 that = this,
516 options = that.options,
516 options = that.options,
517 serviceUrl = options.serviceUrl,
517 serviceUrl = options.serviceUrl,
518 params,
518 params,
519 cacheKey,
519 cacheKey,
520 ajaxSettings;
520 ajaxSettings;
521
521
522 options.params[options.paramName] = query;
522 options.params[options.paramName] = query;
523 params = options.ignoreParams ? null : options.params;
523 params = options.ignoreParams ? null : options.params;
524
524
525 if (that.isLocal) {
525 if (that.isLocal) {
526 response = that.getSuggestionsLocal(query);
526 response = that.getSuggestionsLocal(query);
527 } else {
527 } else {
528 if ($.isFunction(serviceUrl)) {
528 if ($.isFunction(serviceUrl)) {
529 serviceUrl = serviceUrl.call(that.element, query);
529 serviceUrl = serviceUrl.call(that.element, query);
530 }
530 }
531 cacheKey = serviceUrl + '?' + $.param(params || {});
531
532 var callParams = {};
533 //make an evaluated copy of params
534 $.each(params, function(index, value) {
535 if($.isFunction(value)){
536 callParams[index] = value();
537 }
538 else {
539 callParams[index] = value;
540 }
541 });
542
543 cacheKey = serviceUrl + '?' + $.param(callParams);
532 response = that.cachedResponse[cacheKey];
544 response = that.cachedResponse[cacheKey];
533 }
545 }
534
546
535 if (response && $.isArray(response.suggestions)) {
547 if (response && $.isArray(response.suggestions)) {
536 that.suggestions = response.suggestions;
548 that.suggestions = response.suggestions;
537 that.suggest();
549 that.suggest();
538 } else if (!that.isBadQuery(query)) {
550 } else if (!that.isBadQuery(query)) {
539 if (options.onSearchStart.call(that.element, options.params) === false) {
551 if (options.onSearchStart.call(that.element, params) === false) {
540 return;
552 return;
541 }
553 }
542 if (that.currentRequest) {
554 if (that.currentRequest) {
543 that.currentRequest.abort();
555 that.currentRequest.abort();
544 }
556 }
545
557
546 ajaxSettings = {
558 ajaxSettings = {
547 url: serviceUrl,
559 url: serviceUrl,
548 data: params,
560 data: params,
549 type: options.type,
561 type: options.type,
550 dataType: options.dataType
562 dataType: options.dataType
551 };
563 };
552
564
553 $.extend(ajaxSettings, options.ajaxSettings);
565 $.extend(ajaxSettings, options.ajaxSettings);
554
566
555 that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
567 that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
556 var result;
568 var result;
557 that.currentRequest = null;
569 that.currentRequest = null;
558 result = options.transformResult(data);
570 result = options.transformResult(data);
559 that.processResponse(result, query, cacheKey);
571 that.processResponse(result, query, cacheKey);
560 options.onSearchComplete.call(that.element, query, result.suggestions);
572 options.onSearchComplete.call(that.element, query, result.suggestions);
561 }).fail(function (jqXHR, textStatus, errorThrown) {
573 }).fail(function (jqXHR, textStatus, errorThrown) {
562 options.onSearchError.call(that.element, query, jqXHR, textStatus, errorThrown);
574 options.onSearchError.call(that.element, query, jqXHR, textStatus, errorThrown);
563 });
575 });
564 }
576 }
565 },
577 },
566
578
567 isBadQuery: function (q) {
579 isBadQuery: function (q) {
568 if (!this.options.preventBadQueries){
580 if (!this.options.preventBadQueries){
569 return false;
581 return false;
570 }
582 }
571
583
572 var badQueries = this.badQueries,
584 var badQueries = this.badQueries,
573 i = badQueries.length;
585 i = badQueries.length;
574
586
575 while (i--) {
587 while (i--) {
576 if (q.indexOf(badQueries[i]) === 0) {
588 if (q.indexOf(badQueries[i]) === 0) {
577 return true;
589 return true;
578 }
590 }
579 }
591 }
580
592
581 return false;
593 return false;
582 },
594 },
583
595
584 hide: function () {
596 hide: function () {
585 var that = this;
597 var that = this;
586 that.visible = false;
598 that.visible = false;
587 that.selectedIndex = -1;
599 that.selectedIndex = -1;
588 $(that.suggestionsContainer).hide();
600 $(that.suggestionsContainer).hide();
589 that.signalHint(null);
601 that.signalHint(null);
590 },
602 },
591
603
592 suggest: function () {
604 suggest: function () {
593
605
594 var that = this,
606 var that = this,
595 options = that.options,
607 options = that.options,
596 formatResult = options.formatResult,
608 formatResult = options.formatResult,
597 filterResult = options.lookupFilter,
609 filterResult = options.lookupFilter,
598 value = that.getQuery(that.currentValue),
610 value = that.getQuery(that.currentValue),
599 className = that.classes.suggestion,
611 className = that.classes.suggestion,
600 classSelected = that.classes.selected,
612 classSelected = that.classes.selected,
601 container = $(that.suggestionsContainer),
613 container = $(that.suggestionsContainer),
602 noSuggestionsContainer = $(that.noSuggestionsContainer),
614 noSuggestionsContainer = $(that.noSuggestionsContainer),
603 beforeRender = options.beforeRender,
615 beforeRender = options.beforeRender,
604 limit = parseInt(that.options.lookupLimit, 10),
616 limit = parseInt(that.options.lookupLimit, 10),
605 html = '',
617 html = '',
606 index;
618 index;
607
619
608 // filter and limit given results
620 // filter and limit given results
609 var filtered_suggestions = $.grep(that.suggestions, function (suggestion) {
621 var filtered_suggestions = $.grep(that.suggestions, function (suggestion) {
610 return filterResult(suggestion, value, value.toLowerCase(), that.element);
622 return filterResult(suggestion, value, value.toLowerCase(), that.element);
611 });
623 });
612
624
613 if (limit && filtered_suggestions.length > limit) {
625 if (limit && filtered_suggestions.length > limit) {
614 filtered_suggestions = filtered_suggestions.slice(0, limit);
626 filtered_suggestions = filtered_suggestions.slice(0, limit);
615 }
627 }
616
628
617 if (filtered_suggestions.length === 0) {
629 if (filtered_suggestions.length === 0) {
618 this.options.showNoSuggestionNotice ? this.noSuggestions() : this.hide();
630 this.options.showNoSuggestionNotice ? this.noSuggestions() : this.hide();
619 return;
631 return;
620 }
632 }
621
633
622 if (options.triggerSelectOnValidInput) {
634 if (options.triggerSelectOnValidInput) {
623 index = that.findSuggestionIndex(value);
635 index = that.findSuggestionIndex(value);
624 if (index !== -1) {
636 if (index !== -1) {
625 that.select(index);
637 that.select(index);
626 return;
638 return;
627 }
639 }
628 }
640 }
629
641
630 // Build suggestions inner HTML:
642 // Build suggestions inner HTML:
631 $.each(filtered_suggestions, function (i, suggestion) {
643 $.each(filtered_suggestions, function (i, suggestion) {
632 html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value, Autocomplete.formatResult, that.element) + '</div>';
644 html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value, Autocomplete.formatResult, that.element) + '</div>';
633 });
645 });
634 // set internal suggestion for INDEX pick to work correctly
646 // set internal suggestion for INDEX pick to work correctly
635 that.suggestions = filtered_suggestions;
647 that.suggestions = filtered_suggestions;
636 this.adjustContainerWidth();
648 this.adjustContainerWidth();
637
649
638 noSuggestionsContainer.detach();
650 noSuggestionsContainer.detach();
639 container.html(html);
651 container.html(html);
640
652
641 // Select first value by default:
653 // Select first value by default:
642 if (options.autoSelectFirst) {
654 if (options.autoSelectFirst) {
643 that.selectedIndex = 0;
655 that.selectedIndex = 0;
644 container.children().first().addClass(classSelected);
656 container.children().first().addClass(classSelected);
645 }
657 }
646
658
647 if ($.isFunction(beforeRender)) {
659 if ($.isFunction(beforeRender)) {
648 beforeRender.call(that.element, container);
660 beforeRender.call(that.element, container);
649 }
661 }
650
662
651 that.fixPosition();
663 that.fixPosition();
652
664
653 container.show();
665 container.show();
654 that.visible = true;
666 that.visible = true;
655
667
656 that.findBestHint();
668 that.findBestHint();
657 },
669 },
658
670
659 noSuggestions: function() {
671 noSuggestions: function() {
660 var that = this,
672 var that = this,
661 container = $(that.suggestionsContainer),
673 container = $(that.suggestionsContainer),
662 noSuggestionsContainer = $(that.noSuggestionsContainer);
674 noSuggestionsContainer = $(that.noSuggestionsContainer);
663
675
664 this.adjustContainerWidth();
676 this.adjustContainerWidth();
665
677
666 // Some explicit steps. Be careful here as it easy to get
678 // Some explicit steps. Be careful here as it easy to get
667 // noSuggestionsContainer removed from DOM if not detached properly.
679 // noSuggestionsContainer removed from DOM if not detached properly.
668 noSuggestionsContainer.detach();
680 noSuggestionsContainer.detach();
669 container.empty(); // clean suggestions if any
681 container.empty(); // clean suggestions if any
670 container.append(noSuggestionsContainer);
682 container.append(noSuggestionsContainer);
671
683
672 that.fixPosition();
684 that.fixPosition();
673
685
674 container.show();
686 container.show();
675 that.visible = true;
687 that.visible = true;
676 },
688 },
677
689
678 adjustContainerWidth: function() {
690 adjustContainerWidth: function() {
679 var that = this,
691 var that = this,
680 options = that.options,
692 options = that.options,
681 width,
693 width,
682 container = $(that.suggestionsContainer);
694 container = $(that.suggestionsContainer);
683
695
684 // If width is auto, adjust width before displaying suggestions,
696 // If width is auto, adjust width before displaying suggestions,
685 // because if instance was created before input had width, it will be zero.
697 // because if instance was created before input had width, it will be zero.
686 // Also it adjusts if input width has changed.
698 // Also it adjusts if input width has changed.
687 // -2px to account for suggestions border.
699 // -2px to account for suggestions border.
688 if (options.width === 'auto') {
700 if (options.width === 'auto') {
689 width = that.el.outerWidth() - 2;
701 width = that.el.outerWidth() - 2;
690 container.width(width > 0 ? width : 300);
702 container.width(width > 0 ? width : 300);
691 }
703 }
692 },
704 },
693
705
694 findBestHint: function () {
706 findBestHint: function () {
695 var that = this,
707 var that = this,
696 value = that.el.val().toLowerCase(),
708 value = that.el.val().toLowerCase(),
697 bestMatch = null;
709 bestMatch = null;
698
710
699 if (!value) {
711 if (!value) {
700 return;
712 return;
701 }
713 }
702
714
703 $.each(that.suggestions, function (i, suggestion) {
715 $.each(that.suggestions, function (i, suggestion) {
704 var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
716 var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
705 if (foundMatch) {
717 if (foundMatch) {
706 bestMatch = suggestion;
718 bestMatch = suggestion;
707 }
719 }
708 return !foundMatch;
720 return !foundMatch;
709 });
721 });
710 that.signalHint(bestMatch);
722 that.signalHint(bestMatch);
711 },
723 },
712
724
713 signalHint: function (suggestion) {
725 signalHint: function (suggestion) {
714 var hintValue = '',
726 var hintValue = '',
715 that = this;
727 that = this;
716 if (suggestion) {
728 if (suggestion) {
717 hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
729 hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
718 }
730 }
719 if (that.hintValue !== hintValue) {
731 if (that.hintValue !== hintValue) {
720 that.hintValue = hintValue;
732 that.hintValue = hintValue;
721 that.hint = suggestion;
733 that.hint = suggestion;
722 (this.options.onHint || $.noop)(hintValue);
734 (this.options.onHint || $.noop)(hintValue);
723 }
735 }
724 },
736 },
725
737
726 verifySuggestionsFormat: function (suggestions) {
738 verifySuggestionsFormat: function (suggestions) {
727 // If suggestions is string array, convert them to supported format:
739 // If suggestions is string array, convert them to supported format:
728 if (suggestions.length && typeof suggestions[0] === 'string') {
740 if (suggestions.length && typeof suggestions[0] === 'string') {
729 return $.map(suggestions, function (value) {
741 return $.map(suggestions, function (value) {
730 return { value: value, data: null };
742 return { value: value, data: null };
731 });
743 });
732 }
744 }
733
745
734 return suggestions;
746 return suggestions;
735 },
747 },
736
748
737 validateOrientation: function(orientation, fallback) {
749 validateOrientation: function(orientation, fallback) {
738 orientation = $.trim(orientation || '').toLowerCase();
750 orientation = $.trim(orientation || '').toLowerCase();
739
751
740 if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
752 if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
741 orientation = fallback;
753 orientation = fallback;
742 }
754 }
743
755
744 return orientation;
756 return orientation;
745 },
757 },
746
758
747 processResponse: function (result, originalQuery, cacheKey) {
759 processResponse: function (result, originalQuery, cacheKey) {
748 var that = this,
760 var that = this,
749 options = that.options;
761 options = that.options;
750
762
751 result.suggestions = that.verifySuggestionsFormat(result.suggestions);
763 result.suggestions = that.verifySuggestionsFormat(result.suggestions);
752
764
753 // Cache results if cache is not disabled:
765 // Cache results if cache is not disabled:
754 if (!options.noCache) {
766 if (!options.noCache) {
755 that.cachedResponse[cacheKey] = result;
767 that.cachedResponse[cacheKey] = result;
756 if (options.preventBadQueries && result.suggestions.length === 0) {
768 if (options.preventBadQueries && result.suggestions.length === 0) {
757 that.badQueries.push(originalQuery);
769 that.badQueries.push(originalQuery);
758 }
770 }
759 }
771 }
760
772
761 // Return if originalQuery is not matching current query:
773 // Return if originalQuery is not matching current query:
762 if (originalQuery !== that.getQuery(that.currentValue)) {
774 if (originalQuery !== that.getQuery(that.currentValue)) {
763 return;
775 return;
764 }
776 }
765
777
766 that.suggestions = result.suggestions;
778 that.suggestions = result.suggestions;
767 that.suggest();
779 that.suggest();
768 },
780 },
769
781
770 activate: function (index) {
782 activate: function (index) {
771 var that = this,
783 var that = this,
772 activeItem,
784 activeItem,
773 selected = that.classes.selected,
785 selected = that.classes.selected,
774 container = $(that.suggestionsContainer),
786 container = $(that.suggestionsContainer),
775 children = container.find('.' + that.classes.suggestion);
787 children = container.find('.' + that.classes.suggestion);
776
788
777 container.find('.' + selected).removeClass(selected);
789 container.find('.' + selected).removeClass(selected);
778
790
779 that.selectedIndex = index;
791 that.selectedIndex = index;
780
792
781 if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
793 if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
782 activeItem = children.get(that.selectedIndex);
794 activeItem = children.get(that.selectedIndex);
783 $(activeItem).addClass(selected);
795 $(activeItem).addClass(selected);
784 return activeItem;
796 return activeItem;
785 }
797 }
786
798
787 return null;
799 return null;
788 },
800 },
789
801
790 selectHint: function () {
802 selectHint: function () {
791 var that = this,
803 var that = this,
792 i = $.inArray(that.hint, that.suggestions);
804 i = $.inArray(that.hint, that.suggestions);
793 that.select(i);
805 that.select(i);
794 },
806 },
795
807
796 select: function (index) {
808 select: function (index) {
797 var that = this;
809 var that = this;
798 that.hide();
810 that.hide();
799 that.onSelect(index);
811 that.onSelect(index);
800 },
812 },
801
813
802 moveUp: function () {
814 moveUp: function () {
803 var that = this;
815 var that = this;
804
816
805 if (that.selectedIndex === -1) {
817 if (that.selectedIndex === -1) {
806 return;
818 return;
807 }
819 }
808
820
809 if (that.selectedIndex === 0) {
821 if (that.selectedIndex === 0) {
810 $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
822 $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
811 that.selectedIndex = -1;
823 that.selectedIndex = -1;
812 that.el.val(that.currentValue);
824 that.el.val(that.currentValue);
813 that.findBestHint();
825 that.findBestHint();
814 return;
826 return;
815 }
827 }
816
828
817 that.adjustScroll(that.selectedIndex - 1);
829 that.adjustScroll(that.selectedIndex - 1);
818 },
830 },
819
831
820 moveDown: function () {
832 moveDown: function () {
821 var that = this;
833 var that = this;
822
834
823 if (that.selectedIndex === (that.suggestions.length - 1)) {
835 if (that.selectedIndex === (that.suggestions.length - 1)) {
824 return;
836 return;
825 }
837 }
826
838
827 that.adjustScroll(that.selectedIndex + 1);
839 that.adjustScroll(that.selectedIndex + 1);
828 },
840 },
829
841
830 adjustScroll: function (index) {
842 adjustScroll: function (index) {
831 var that = this,
843 var that = this,
832 activeItem = that.activate(index),
844 activeItem = that.activate(index),
833 offsetTop,
845 offsetTop,
834 upperBound,
846 upperBound,
835 lowerBound,
847 lowerBound,
836 heightDelta = 25;
848 heightDelta = 25;
837
849
838 if (!activeItem) {
850 if (!activeItem) {
839 return;
851 return;
840 }
852 }
841
853
842 offsetTop = activeItem.offsetTop;
854 offsetTop = activeItem.offsetTop;
843 upperBound = $(that.suggestionsContainer).scrollTop();
855 upperBound = $(that.suggestionsContainer).scrollTop();
844 lowerBound = upperBound + that.options.maxHeight - heightDelta;
856 lowerBound = upperBound + that.options.maxHeight - heightDelta;
845
857
846 if (offsetTop < upperBound) {
858 if (offsetTop < upperBound) {
847 $(that.suggestionsContainer).scrollTop(offsetTop);
859 $(that.suggestionsContainer).scrollTop(offsetTop);
848 } else if (offsetTop > lowerBound) {
860 } else if (offsetTop > lowerBound) {
849 $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
861 $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
850 }
862 }
851
863
852 if (that.options.replaceOnArrowKey) {
864 if (that.options.replaceOnArrowKey) {
853 that.el.val(that.getValue(that.suggestions[index].value));
865 that.el.val(that.getValue(that.suggestions[index].value));
854 }
866 }
855 that.signalHint(null);
867 that.signalHint(null);
856 },
868 },
857
869
858 onSelect: function (index) {
870 onSelect: function (index) {
859 var that = this,
871 var that = this,
860 onSelectCallback = that.options.onSelect,
872 onSelectCallback = that.options.onSelect,
861 suggestion = that.suggestions[index];
873 suggestion = that.suggestions[index];
862
874
863 that.currentValue = that.getValue(suggestion.value);
875 that.currentValue = that.getValue(suggestion.value);
864 var prevElem = {'value': that.el.val(),
876 var prevElem = {'value': that.el.val(),
865 'caret': that.element.selectionStart}
877 'caret': that.element.selectionStart}
866
878
867 if (that.currentValue !== that.el.val()) {
879 if (that.currentValue !== that.el.val()) {
868 that.el.val(that.currentValue);
880 that.el.val(that.currentValue);
869 }
881 }
870
882
871 that.signalHint(null);
883 that.signalHint(null);
872 that.suggestions = [];
884 that.suggestions = [];
873 that.selection = suggestion;
885 that.selection = suggestion;
874
886
875 if ($.isFunction(onSelectCallback)) {
887 if ($.isFunction(onSelectCallback)) {
876 onSelectCallback.call(this, that.element, suggestion, prevElem);
888 onSelectCallback.call(this, that.element, suggestion, prevElem);
877 }
889 }
878 },
890 },
879
891
880 getValue: function (value) {
892 getValue: function (value) {
881 var that = this,
893 var that = this,
882 delimiter = that.options.delimiter,
894 delimiter = that.options.delimiter,
883 currentValue,
895 currentValue,
884 parts;
896 parts;
885
897
886 if (!delimiter) {
898 if (!delimiter) {
887 return value;
899 return value;
888 }
900 }
889
901
890 currentValue = that.currentValue;
902 currentValue = that.currentValue;
891 parts = currentValue.split(delimiter);
903 parts = currentValue.split(delimiter);
892
904
893 if (parts.length === 1) {
905 if (parts.length === 1) {
894 return value;
906 return value;
895 }
907 }
896
908
897 return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
909 return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
898 },
910 },
899
911
900 dispose: function () {
912 dispose: function () {
901 var that = this;
913 var that = this;
902 that.el.off('.autocomplete').removeData('autocomplete');
914 that.el.off('.autocomplete').removeData('autocomplete');
903 that.disableKillerFn();
915 that.disableKillerFn();
904 $(window).off('resize.autocomplete', that.fixPositionCapture);
916 $(window).off('resize.autocomplete', that.fixPositionCapture);
905 $(that.suggestionsContainer).remove();
917 $(that.suggestionsContainer).remove();
906 }
918 }
907 };
919 };
908
920
909 // Create chainable jQuery plugin:
921 // Create chainable jQuery plugin:
910 $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
922 $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
911 var dataKey = 'autocomplete';
923 var dataKey = 'autocomplete';
912 // If function invoked without argument return
924 // If function invoked without argument return
913 // instance of the first matched element:
925 // instance of the first matched element:
914 if (arguments.length === 0) {
926 if (arguments.length === 0) {
915 return this.first().data(dataKey);
927 return this.first().data(dataKey);
916 }
928 }
917
929
918 return this.each(function () {
930 return this.each(function () {
919 var inputElement = $(this),
931 var inputElement = $(this),
920 instance = inputElement.data(dataKey);
932 instance = inputElement.data(dataKey);
921
933
922 if (typeof options === 'string') {
934 if (typeof options === 'string') {
923 if (instance && typeof instance[options] === 'function') {
935 if (instance && typeof instance[options] === 'function') {
924 instance[options](args);
936 instance[options](args);
925 }
937 }
926 } else {
938 } else {
927 // If instance already exists, destroy it:
939 // If instance already exists, destroy it:
928 if (instance && instance.dispose) {
940 if (instance && instance.dispose) {
929 instance.dispose();
941 instance.dispose();
930 }
942 }
931 instance = new Autocomplete(this, options);
943 instance = new Autocomplete(this, options);
932 inputElement.data(dataKey, instance);
944 inputElement.data(dataKey, instance);
933 }
945 }
934 });
946 });
935 };
947 };
936 }));
948 }));
@@ -1,832 +1,910 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <%include file="/ejs_templates/templates.html"/>
4 <%include file="/ejs_templates/templates.html"/>
5
5
6 <div class="outerwrapper">
6 <div class="outerwrapper">
7 <!-- HEADER -->
7 <!-- HEADER -->
8 <div class="header">
8 <div class="header">
9 <div id="header-inner" class="wrapper">
9 <div id="header-inner" class="wrapper">
10 <div id="logo">
10 <div id="logo">
11 <div class="logo-wrapper">
11 <div class="logo-wrapper">
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 % if c.rhodecode_name:
14 % if c.rhodecode_name:
15 <div class="branding">
15 <div class="branding">
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 </div>
17 </div>
18 % endif
18 % endif
19 </div>
19 </div>
20 <!-- MENU BAR NAV -->
20 <!-- MENU BAR NAV -->
21 ${self.menu_bar_nav()}
21 ${self.menu_bar_nav()}
22 <!-- END MENU BAR NAV -->
22 <!-- END MENU BAR NAV -->
23 </div>
23 </div>
24 </div>
24 </div>
25 ${self.menu_bar_subnav()}
25 ${self.menu_bar_subnav()}
26 <!-- END HEADER -->
26 <!-- END HEADER -->
27
27
28 <!-- CONTENT -->
28 <!-- CONTENT -->
29 <div id="content" class="wrapper">
29 <div id="content" class="wrapper">
30
30
31 <rhodecode-toast id="notifications"></rhodecode-toast>
31 <rhodecode-toast id="notifications"></rhodecode-toast>
32
32
33 <div class="main">
33 <div class="main">
34 ${next.main()}
34 ${next.main()}
35 </div>
35 </div>
36 </div>
36 </div>
37 <!-- END CONTENT -->
37 <!-- END CONTENT -->
38
38
39 </div>
39 </div>
40 <!-- FOOTER -->
40 <!-- FOOTER -->
41 <div id="footer">
41 <div id="footer">
42 <div id="footer-inner" class="title wrapper">
42 <div id="footer-inner" class="title wrapper">
43 <div>
43 <div>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 % if c.visual.show_version:
45 % if c.visual.show_version:
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 % endif
47 % endif
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 % if c.visual.rhodecode_support_url:
49 % if c.visual.rhodecode_support_url:
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 % endif
51 % endif
52 </p>
52 </p>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 <p class="server-instance" style="display:${sid}">
54 <p class="server-instance" style="display:${sid}">
55 ## display hidden instance ID if specially defined
55 ## display hidden instance ID if specially defined
56 % if c.rhodecode_instanceid:
56 % if c.rhodecode_instanceid:
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 % endif
58 % endif
59 </p>
59 </p>
60 </div>
60 </div>
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <!-- END FOOTER -->
64 <!-- END FOOTER -->
65
65
66 ### MAKO DEFS ###
66 ### MAKO DEFS ###
67
67
68 <%def name="menu_bar_subnav()">
68 <%def name="menu_bar_subnav()">
69 </%def>
69 </%def>
70
70
71 <%def name="breadcrumbs(class_='breadcrumbs')">
71 <%def name="breadcrumbs(class_='breadcrumbs')">
72 <div class="${class_}">
72 <div class="${class_}">
73 ${self.breadcrumbs_links()}
73 ${self.breadcrumbs_links()}
74 </div>
74 </div>
75 </%def>
75 </%def>
76
76
77 <%def name="admin_menu()">
77 <%def name="admin_menu()">
78 <ul class="admin_menu submenu">
78 <ul class="admin_menu submenu">
79 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
79 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
80 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
80 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
81 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
81 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
82 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
82 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
83 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
83 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
84 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
84 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
85 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
85 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
86 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
86 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
87 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
87 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
88 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
88 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
89 </ul>
89 </ul>
90 </%def>
90 </%def>
91
91
92
92
93 <%def name="dt_info_panel(elements)">
93 <%def name="dt_info_panel(elements)">
94 <dl class="dl-horizontal">
94 <dl class="dl-horizontal">
95 %for dt, dd, title, show_items in elements:
95 %for dt, dd, title, show_items in elements:
96 <dt>${dt}:</dt>
96 <dt>${dt}:</dt>
97 <dd title="${h.tooltip(title)}">
97 <dd title="${h.tooltip(title)}">
98 %if callable(dd):
98 %if callable(dd):
99 ## allow lazy evaluation of elements
99 ## allow lazy evaluation of elements
100 ${dd()}
100 ${dd()}
101 %else:
101 %else:
102 ${dd}
102 ${dd}
103 %endif
103 %endif
104 %if show_items:
104 %if show_items:
105 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
105 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
106 %endif
106 %endif
107 </dd>
107 </dd>
108
108
109 %if show_items:
109 %if show_items:
110 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
110 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
111 %for item in show_items:
111 %for item in show_items:
112 <dt></dt>
112 <dt></dt>
113 <dd>${item}</dd>
113 <dd>${item}</dd>
114 %endfor
114 %endfor
115 </div>
115 </div>
116 %endif
116 %endif
117
117
118 %endfor
118 %endfor
119 </dl>
119 </dl>
120 </%def>
120 </%def>
121
121
122
122
123 <%def name="gravatar(email, size=16)">
123 <%def name="gravatar(email, size=16)">
124 <%
124 <%
125 if (size > 16):
125 if (size > 16):
126 gravatar_class = 'gravatar gravatar-large'
126 gravatar_class = 'gravatar gravatar-large'
127 else:
127 else:
128 gravatar_class = 'gravatar'
128 gravatar_class = 'gravatar'
129 %>
129 %>
130 <%doc>
130 <%doc>
131 TODO: johbo: For now we serve double size images to make it smooth
131 TODO: johbo: For now we serve double size images to make it smooth
132 for retina. This is how it worked until now. Should be replaced
132 for retina. This is how it worked until now. Should be replaced
133 with a better solution at some point.
133 with a better solution at some point.
134 </%doc>
134 </%doc>
135 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
135 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
136 </%def>
136 </%def>
137
137
138
138
139 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
139 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
140 <% email = h.email_or_none(contact) %>
140 <% email = h.email_or_none(contact) %>
141 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
141 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
142 ${self.gravatar(email, size)}
142 ${self.gravatar(email, size)}
143 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
143 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
144 </div>
144 </div>
145 </%def>
145 </%def>
146
146
147
147
148 ## admin menu used for people that have some admin resources
148 ## admin menu used for people that have some admin resources
149 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
149 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
150 <ul class="submenu">
150 <ul class="submenu">
151 %if repositories:
151 %if repositories:
152 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
152 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
153 %endif
153 %endif
154 %if repository_groups:
154 %if repository_groups:
155 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
155 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
156 %endif
156 %endif
157 %if user_groups:
157 %if user_groups:
158 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
158 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
159 %endif
159 %endif
160 </ul>
160 </ul>
161 </%def>
161 </%def>
162
162
163 <%def name="repo_page_title(repo_instance)">
163 <%def name="repo_page_title(repo_instance)">
164 <div class="title-content">
164 <div class="title-content">
165 <div class="title-main">
165 <div class="title-main">
166 ## SVN/HG/GIT icons
166 ## SVN/HG/GIT icons
167 %if h.is_hg(repo_instance):
167 %if h.is_hg(repo_instance):
168 <i class="icon-hg"></i>
168 <i class="icon-hg"></i>
169 %endif
169 %endif
170 %if h.is_git(repo_instance):
170 %if h.is_git(repo_instance):
171 <i class="icon-git"></i>
171 <i class="icon-git"></i>
172 %endif
172 %endif
173 %if h.is_svn(repo_instance):
173 %if h.is_svn(repo_instance):
174 <i class="icon-svn"></i>
174 <i class="icon-svn"></i>
175 %endif
175 %endif
176
176
177 ## public/private
177 ## public/private
178 %if repo_instance.private:
178 %if repo_instance.private:
179 <i class="icon-repo-private"></i>
179 <i class="icon-repo-private"></i>
180 %else:
180 %else:
181 <i class="icon-repo-public"></i>
181 <i class="icon-repo-public"></i>
182 %endif
182 %endif
183
183
184 ## repo name with group name
184 ## repo name with group name
185 ${h.breadcrumb_repo_link(repo_instance)}
185 ${h.breadcrumb_repo_link(repo_instance)}
186
186
187 </div>
187 </div>
188
188
189 ## FORKED
189 ## FORKED
190 %if repo_instance.fork:
190 %if repo_instance.fork:
191 <p>
191 <p>
192 <i class="icon-code-fork"></i> ${_('Fork of')}
192 <i class="icon-code-fork"></i> ${_('Fork of')}
193 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
193 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
194 </p>
194 </p>
195 %endif
195 %endif
196
196
197 ## IMPORTED FROM REMOTE
197 ## IMPORTED FROM REMOTE
198 %if repo_instance.clone_uri:
198 %if repo_instance.clone_uri:
199 <p>
199 <p>
200 <i class="icon-code-fork"></i> ${_('Clone from')}
200 <i class="icon-code-fork"></i> ${_('Clone from')}
201 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
201 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
202 </p>
202 </p>
203 %endif
203 %endif
204
204
205 ## LOCKING STATUS
205 ## LOCKING STATUS
206 %if repo_instance.locked[0]:
206 %if repo_instance.locked[0]:
207 <p class="locking_locked">
207 <p class="locking_locked">
208 <i class="icon-repo-lock"></i>
208 <i class="icon-repo-lock"></i>
209 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
209 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
210 </p>
210 </p>
211 %elif repo_instance.enable_locking:
211 %elif repo_instance.enable_locking:
212 <p class="locking_unlocked">
212 <p class="locking_unlocked">
213 <i class="icon-repo-unlock"></i>
213 <i class="icon-repo-unlock"></i>
214 ${_('Repository not locked. Pull repository to lock it.')}
214 ${_('Repository not locked. Pull repository to lock it.')}
215 </p>
215 </p>
216 %endif
216 %endif
217
217
218 </div>
218 </div>
219 </%def>
219 </%def>
220
220
221 <%def name="repo_menu(active=None)">
221 <%def name="repo_menu(active=None)">
222 <%
222 <%
223 def is_active(selected):
223 def is_active(selected):
224 if selected == active:
224 if selected == active:
225 return "active"
225 return "active"
226 %>
226 %>
227
227
228 <!--- CONTEXT BAR -->
228 <!--- CONTEXT BAR -->
229 <div id="context-bar">
229 <div id="context-bar">
230 <div class="wrapper">
230 <div class="wrapper">
231 <ul id="context-pages" class="navigation horizontal-list">
231 <ul id="context-pages" class="navigation horizontal-list">
232 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
232 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
233 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
233 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
234 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
234 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
235 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
235 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
236 <li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Search')}</div></a></li>
237
236
238 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
237 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
239 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
238 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
240 <li class="${is_active('showpullrequest')}">
239 <li class="${is_active('showpullrequest')}">
241 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
240 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
242 %if c.repository_pull_requests:
241 %if c.repository_pull_requests:
243 <span class="pr_notifications">${c.repository_pull_requests}</span>
242 <span class="pr_notifications">${c.repository_pull_requests}</span>
244 %endif
243 %endif
245 <div class="menulabel">${_('Pull Requests')}</div>
244 <div class="menulabel">${_('Pull Requests')}</div>
246 </a>
245 </a>
247 </li>
246 </li>
248 %endif
247 %endif
249
248
250 <li class="${is_active('options')}">
249 <li class="${is_active('options')}">
251 <a class="menulink dropdown">
250 <a class="menulink dropdown">
252 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
251 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
253 </a>
252 </a>
254 <ul class="submenu">
253 <ul class="submenu">
255 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
254 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
256 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Repository Settings')}</a></li>
255 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Repository Settings')}</a></li>
257 %endif
256 %endif
258 %if c.rhodecode_db_repo.fork:
257 %if c.rhodecode_db_repo.fork:
259 <li>
258 <li>
260 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
259 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
261 href="${h.route_path('repo_compare',
260 href="${h.route_path('repo_compare',
262 repo_name=c.rhodecode_db_repo.fork.repo_name,
261 repo_name=c.rhodecode_db_repo.fork.repo_name,
263 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
262 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
264 source_ref=c.rhodecode_db_repo.landing_rev[1],
263 source_ref=c.rhodecode_db_repo.landing_rev[1],
265 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
264 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
266 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
265 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
267 _query=dict(merge=1))}"
266 _query=dict(merge=1))}"
268 >
267 >
269 ${_('Compare fork')}
268 ${_('Compare fork')}
270 </a>
269 </a>
271 </li>
270 </li>
272 %endif
271 %endif
273
272
274 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
273 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
275 %if c.rhodecode_db_repo.locked[0]:
274 %if c.rhodecode_db_repo.locked[0]:
276 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
275 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
277 %else:
276 %else:
278 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
277 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
279 %endif
278 %endif
280 %endif
279 %endif
281 %if c.rhodecode_user.username != h.DEFAULT_USER:
280 %if c.rhodecode_user.username != h.DEFAULT_USER:
282 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
281 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
283 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
282 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
284 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
283 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
285 %endif
284 %endif
286 %endif
285 %endif
287 </ul>
286 </ul>
288 </li>
287 </li>
289 </ul>
288 </ul>
290 </div>
289 </div>
291 <div class="clear"></div>
290 <div class="clear"></div>
292 </div>
291 </div>
293 % if c.rhodecode_db_repo.archived:
292 % if c.rhodecode_db_repo.archived:
294 <div class="alert alert-warning text-center">
293 <div class="alert alert-warning text-center">
295 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
294 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
296 </div>
295 </div>
297 % endif
296 % endif
298 <!--- END CONTEXT BAR -->
297 <!--- END CONTEXT BAR -->
299
298
300 </%def>
299 </%def>
301
300
302 <%def name="repo_group_page_title(repo_group_instance)">
301 <%def name="repo_group_page_title(repo_group_instance)">
303 <div class="title-content">
302 <div class="title-content">
304 <div class="title-main">
303 <div class="title-main">
305 ## Repository Group icon
304 ## Repository Group icon
306 <i class="icon-folder-close"></i>
305 <i class="icon-folder-close"></i>
307
306
308 ## repo name with group name
307 ## repo name with group name
309 ${h.breadcrumb_repo_group_link(repo_group_instance)}
308 ${h.breadcrumb_repo_group_link(repo_group_instance)}
310 </div>
309 </div>
311
310
312 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
311 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
313 <div class="repo-group-desc">
312 <div class="repo-group-desc">
314 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
313 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
315 </div>
314 </div>
316
315
317 </div>
316 </div>
318 </%def>
317 </%def>
319
318
320 <%def name="repo_group_menu(active=None)">
319 <%def name="repo_group_menu(active=None)">
321 <%
320 <%
322 def is_active(selected):
321 def is_active(selected):
323 if selected == active:
322 if selected == active:
324 return "active"
323 return "active"
325
324
326 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
325 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
327
326
328 gr_name = c.repo_group.group_name if c.repo_group else None
327 gr_name = c.repo_group.group_name if c.repo_group else None
329 # create repositories with write permission on group is set to true
328 # create repositories with write permission on group is set to true
330 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
329 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
331 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
330 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
332 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
331 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
333
332
334 %>
333 %>
335
334
336 <!--- CONTEXT BAR -->
335 <!--- CONTEXT BAR -->
337 <div id="context-bar">
336 <div id="context-bar">
338 <div class="wrapper">
337 <div class="wrapper">
339 <ul id="context-pages" class="navigation horizontal-list">
338 <ul id="context-pages" class="navigation horizontal-list">
340 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
339 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
341 <li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo_group', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Search')}</div></a></li>
342
340
343 <li class="${is_active('options')}">
341 <li class="${is_active('options')}">
344 <a class="menulink dropdown">
342 <a class="menulink dropdown">
345 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
343 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
346 </a>
344 </a>
347 <ul class="submenu">
345 <ul class="submenu">
348 %if is_admin or group_admin:
346 %if is_admin or group_admin:
349 <li><a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}">${_('Group Settings')}</a></li>
347 <li><a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}">${_('Group Settings')}</a></li>
350 %endif
348 %endif
351 %if is_admin or group_admin or (group_write and create_on_write):
349 %if is_admin or group_admin or (group_write and create_on_write):
352 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
350 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
353 %endif
351 %endif
354 %if is_admin or group_admin:
352 %if is_admin or group_admin:
355 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
353 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
356 %endif
354 %endif
357 </ul>
355 </ul>
358 </li>
356 </li>
359 </ul>
357 </ul>
360 </div>
358 </div>
361 <div class="clear"></div>
359 <div class="clear"></div>
362 </div>
360 </div>
363
361
364 <!--- END CONTEXT BAR -->
362 <!--- END CONTEXT BAR -->
365
363
366 </%def>
364 </%def>
367
365
368
366
369 <%def name="usermenu(active=False)">
367 <%def name="usermenu(active=False)">
370 ## USER MENU
368 ## USER MENU
371 <li id="quick_login_li" class="${'active' if active else ''}">
369 <li id="quick_login_li" class="${'active' if active else ''}">
372 % if c.rhodecode_user.username == h.DEFAULT_USER:
370 % if c.rhodecode_user.username == h.DEFAULT_USER:
373 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
371 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
374 ${gravatar(c.rhodecode_user.email, 20)}
372 ${gravatar(c.rhodecode_user.email, 20)}
375 <span class="user">
373 <span class="user">
376 <span>${_('Sign in')}</span>
374 <span>${_('Sign in')}</span>
377 </span>
375 </span>
378 </a>
376 </a>
379 % else:
377 % else:
380 ## logged in user
378 ## logged in user
381 <a id="quick_login_link" class="menulink childs">
379 <a id="quick_login_link" class="menulink childs">
382 ${gravatar(c.rhodecode_user.email, 20)}
380 ${gravatar(c.rhodecode_user.email, 20)}
383 <span class="user">
381 <span class="user">
384 <span class="menu_link_user">${c.rhodecode_user.username}</span>
382 <span class="menu_link_user">${c.rhodecode_user.username}</span>
385 <div class="show_more"></div>
383 <div class="show_more"></div>
386 </span>
384 </span>
387 </a>
385 </a>
388 ## subnav with menu for logged in user
386 ## subnav with menu for logged in user
389 <div class="user-menu submenu">
387 <div class="user-menu submenu">
390 <div id="quick_login">
388 <div id="quick_login">
391 %if c.rhodecode_user.username != h.DEFAULT_USER:
389 %if c.rhodecode_user.username != h.DEFAULT_USER:
392 <div class="">
390 <div class="">
393 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
391 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
394 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
392 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
395 <div class="email">${c.rhodecode_user.email}</div>
393 <div class="email">${c.rhodecode_user.email}</div>
396 </div>
394 </div>
397 <div class="">
395 <div class="">
398 <ol class="links">
396 <ol class="links">
399 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
397 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
400 % if c.rhodecode_user.personal_repo_group:
398 % if c.rhodecode_user.personal_repo_group:
401 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
399 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
402 % endif
400 % endif
403 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
401 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
404 ## bookmark-items
402 ## bookmark-items
405 <li class="bookmark-items">
403 <li class="bookmark-items">
406 ${_('Bookmarks')}
404 ${_('Bookmarks')}
407 <div class="pull-right">
405 <div class="pull-right">
408 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
406 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
409 </div>
407 </div>
410 </li>
408 </li>
411 % if not c.bookmark_items:
409 % if not c.bookmark_items:
412 <li>
410 <li>
413 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
411 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
414 </li>
412 </li>
415 % endif
413 % endif
416 % for item in c.bookmark_items:
414 % for item in c.bookmark_items:
417 <li>
415 <li>
418 % if item.repository:
416 % if item.repository:
419 <div>
417 <div>
420 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
418 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
421 <code>${item.position}</code>
419 <code>${item.position}</code>
422 % if item.repository.repo_type == 'hg':
420 % if item.repository.repo_type == 'hg':
423 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
421 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
424 % elif item.repository.repo_type == 'git':
422 % elif item.repository.repo_type == 'git':
425 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
423 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
426 % elif item.repository.repo_type == 'svn':
424 % elif item.repository.repo_type == 'svn':
427 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
425 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
428 % endif
426 % endif
429 ${(item.title or h.shorter(item.repository.repo_name, 30))}
427 ${(item.title or h.shorter(item.repository.repo_name, 30))}
430 </a>
428 </a>
431 </div>
429 </div>
432 % elif item.repository_group:
430 % elif item.repository_group:
433 <div>
431 <div>
434 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
432 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
435 <code>${item.position}</code>
433 <code>${item.position}</code>
436 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
434 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
437 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
435 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
438 </a>
436 </a>
439 </div>
437 </div>
440 % else:
438 % else:
441 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
439 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
442 <code>${item.position}</code>
440 <code>${item.position}</code>
443 ${item.title}
441 ${item.title}
444 </a>
442 </a>
445 % endif
443 % endif
446 </li>
444 </li>
447 % endfor
445 % endfor
448
446
449 <li class="logout">
447 <li class="logout">
450 ${h.secure_form(h.route_path('logout'), request=request)}
448 ${h.secure_form(h.route_path('logout'), request=request)}
451 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
449 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
452 ${h.end_form()}
450 ${h.end_form()}
453 </li>
451 </li>
454 </ol>
452 </ol>
455 </div>
453 </div>
456 %endif
454 %endif
457 </div>
455 </div>
458 </div>
456 </div>
459 ## unread counter
457 ## unread counter
460 <div class="pill_container">
458 <div class="pill_container">
461 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
459 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
462 </div>
460 </div>
463 % endif
461 % endif
464 </li>
462 </li>
465 </%def>
463 </%def>
466
464
467 <%def name="menu_items(active=None)">
465 <%def name="menu_items(active=None)">
468 <%
466 <%
469 def is_active(selected):
467 def is_active(selected):
470 if selected == active:
468 if selected == active:
471 return "active"
469 return "active"
472 return ""
470 return ""
473 %>
471 %>
474
472
475 <ul id="quick" class="main_nav navigation horizontal-list">
473 <ul id="quick" class="main_nav navigation horizontal-list">
476 ## notice box for important system messages
474 ## notice box for important system messages
477 <li style="display: none">
475 <li style="display: none">
478 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
476 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
479 <div class="menulabel-notice" >
477 <div class="menulabel-notice" >
480 0
478 0
481 </div>
479 </div>
482 </a>
480 </a>
483 </li>
481 </li>
484
482
485 ## Main filter
483 ## Main filter
486 <li>
484 <li>
487 <div class="menulabel main_filter_box">
485 <div class="menulabel main_filter_box">
488 <div class="main_filter_input_box">
486 <div class="main_filter_input_box">
487 <ul class="searchItems">
488
489 % if c.template_context['search_context']['repo_id']:
490 <li class="searchTag searchTagFilter searchTagHidable" >
491 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
492 <span class="tag">
493 This repo
494 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
495 </span>
496 ##</a>
497 </li>
498 % elif c.template_context['search_context']['repo_group_id']:
499 <li class="searchTag searchTagFilter searchTagHidable">
500 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
501 <span class="tag">
502 This group
503 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
504 </span>
505 ##</a>
506 </li>
507 % endif
508
509 <li class="searchTagInput">
489 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
510 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
490 </div>
511 </li>
491 <div class="main_filter_help_box">
512 <li class="searchTag searchTagHelp">
492 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
513 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
514 </li>
515 </ul>
493 </div>
516 </div>
494 </div>
517 </div>
495
518
496 <div id="main_filter_help" style="display: none">
519 <div id="main_filter_help" style="display: none">
497 - Use '/' key to quickly access this field.
520 - Use '/' key to quickly access this field.
498
521
499 - Enter a name of repository, or repository group for quick search.
522 - Enter a name of repository, or repository group for quick search.
500
523
501 - Prefix query to allow special search:
524 - Prefix query to allow special search:
502
525
503 user:admin, to search for usernames
526 user:admin, to search for usernames, always global
527
528 user_group:devops, to search for user groups, always global
504
529
505 user_group:devops, to search for user groups
530 commit:efced4, to search for commits, scoped to repositories or groups
531
532 file:models.py, to search for file paths, scoped to repositories or groups
506
533
507 commit:efced4, to search for commits
534 % if c.template_context['search_context']['repo_id']:
508
535 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
509 file:models.py, to search for file paths
536 % elif c.template_context['search_context']['repo_group_id']:
537 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
538 % else:
539 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
540 % endif
510 </div>
541 </div>
511 </li>
542 </li>
512
543
513 ## ROOT MENU
544 ## ROOT MENU
514 <li class="${is_active('home')}">
545 <li class="${is_active('home')}">
515 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
546 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
516 <div class="menulabel">${_('Home')}</div>
547 <div class="menulabel">${_('Home')}</div>
517 </a>
548 </a>
518 </li>
549 </li>
519
550
520 %if c.rhodecode_user.username != h.DEFAULT_USER:
551 %if c.rhodecode_user.username != h.DEFAULT_USER:
521 <li class="${is_active('journal')}">
552 <li class="${is_active('journal')}">
522 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
553 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
523 <div class="menulabel">${_('Journal')}</div>
554 <div class="menulabel">${_('Journal')}</div>
524 </a>
555 </a>
525 </li>
556 </li>
526 %else:
557 %else:
527 <li class="${is_active('journal')}">
558 <li class="${is_active('journal')}">
528 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
559 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
529 <div class="menulabel">${_('Public journal')}</div>
560 <div class="menulabel">${_('Public journal')}</div>
530 </a>
561 </a>
531 </li>
562 </li>
532 %endif
563 %endif
533
564
534 <li class="${is_active('gists')}">
565 <li class="${is_active('gists')}">
535 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
566 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
536 <div class="menulabel">${_('Gists')}</div>
567 <div class="menulabel">${_('Gists')}</div>
537 </a>
568 </a>
538 </li>
569 </li>
539
570
540 % if h.HasPermissionAll('hg.admin')('access admin main page'):
571 % if h.HasPermissionAll('hg.admin')('access admin main page'):
541 <li class="${is_active('admin')}">
572 <li class="${is_active('admin')}">
542 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
573 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
543 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
574 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
544 </a>
575 </a>
545 ${admin_menu()}
576 ${admin_menu()}
546 </li>
577 </li>
547 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
578 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
548 <li class="${is_active('admin')}">
579 <li class="${is_active('admin')}">
549 <a class="menulink childs" title="${_('Delegated Admin settings')}">
580 <a class="menulink childs" title="${_('Delegated Admin settings')}">
550 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
581 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
551 </a>
582 </a>
552 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
583 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
553 c.rhodecode_user.repository_groups_admin,
584 c.rhodecode_user.repository_groups_admin,
554 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
585 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
555 </li>
586 </li>
556 % endif
587 % endif
557 ## render extra user menu
588 ## render extra user menu
558 ${usermenu(active=(active=='my_account'))}
589 ${usermenu(active=(active=='my_account'))}
559
590
560 % if c.debug_style:
591 % if c.debug_style:
561 <li>
592 <li>
562 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
593 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
563 <div class="menulabel">${_('[Style]')}</div>
594 <div class="menulabel">${_('[Style]')}</div>
564 </a>
595 </a>
565 </li>
596 </li>
566 % endif
597 % endif
567 </ul>
598 </ul>
568
599
569 <script type="text/javascript">
600 <script type="text/javascript">
570 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
601 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
571
602
572 var formatRepoResult = function(result, container, query, escapeMarkup) {
603 var formatRepoResult = function(result, container, query, escapeMarkup) {
573 return function(data, escapeMarkup) {
604 return function(data, escapeMarkup) {
574 if (!data.repo_id){
605 if (!data.repo_id){
575 return data.text; // optgroup text Repositories
606 return data.text; // optgroup text Repositories
576 }
607 }
577
608
578 var tmpl = '';
609 var tmpl = '';
579 var repoType = data['repo_type'];
610 var repoType = data['repo_type'];
580 var repoName = data['text'];
611 var repoName = data['text'];
581
612
582 if(data && data.type == 'repo'){
613 if(data && data.type == 'repo'){
583 if(repoType === 'hg'){
614 if(repoType === 'hg'){
584 tmpl += '<i class="icon-hg"></i> ';
615 tmpl += '<i class="icon-hg"></i> ';
585 }
616 }
586 else if(repoType === 'git'){
617 else if(repoType === 'git'){
587 tmpl += '<i class="icon-git"></i> ';
618 tmpl += '<i class="icon-git"></i> ';
588 }
619 }
589 else if(repoType === 'svn'){
620 else if(repoType === 'svn'){
590 tmpl += '<i class="icon-svn"></i> ';
621 tmpl += '<i class="icon-svn"></i> ';
591 }
622 }
592 if(data['private']){
623 if(data['private']){
593 tmpl += '<i class="icon-lock" ></i> ';
624 tmpl += '<i class="icon-lock" ></i> ';
594 }
625 }
595 else if(visualShowPublicIcon){
626 else if(visualShowPublicIcon){
596 tmpl += '<i class="icon-unlock-alt"></i> ';
627 tmpl += '<i class="icon-unlock-alt"></i> ';
597 }
628 }
598 }
629 }
599 tmpl += escapeMarkup(repoName);
630 tmpl += escapeMarkup(repoName);
600 return tmpl;
631 return tmpl;
601
632
602 }(result, escapeMarkup);
633 }(result, escapeMarkup);
603 };
634 };
604
635
605 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
636 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
606 return function(data, escapeMarkup) {
637 return function(data, escapeMarkup) {
607 if (!data.repo_group_id){
638 if (!data.repo_group_id){
608 return data.text; // optgroup text Repositories
639 return data.text; // optgroup text Repositories
609 }
640 }
610
641
611 var tmpl = '';
642 var tmpl = '';
612 var repoGroupName = data['text'];
643 var repoGroupName = data['text'];
613
644
614 if(data){
645 if(data){
615
646
616 tmpl += '<i class="icon-folder-close"></i> ';
647 tmpl += '<i class="icon-folder-close"></i> ';
617
648
618 }
649 }
619 tmpl += escapeMarkup(repoGroupName);
650 tmpl += escapeMarkup(repoGroupName);
620 return tmpl;
651 return tmpl;
621
652
622 }(result, escapeMarkup);
653 }(result, escapeMarkup);
623 };
654 };
624
655
625
626 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
627
628 if (value.split(':').length === 2) {
629 value = value.split(':')[1]
630 }
631
632 var searchType = data['type'];
633 var valueDisplay = data['value_display'];
634
635 var escapeRegExChars = function (value) {
656 var escapeRegExChars = function (value) {
636 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
657 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
637 };
658 };
638 var pattern = '(' + escapeRegExChars(value) + ')';
639
659
640 var getRepoIcon = function(repo_type) {
660 var getRepoIcon = function(repo_type) {
641 if (repo_type === 'hg') {
661 if (repo_type === 'hg') {
642 return '<i class="icon-hg"></i> ';
662 return '<i class="icon-hg"></i> ';
643 }
663 }
644 else if (repo_type === 'git') {
664 else if (repo_type === 'git') {
645 return '<i class="icon-git"></i> ';
665 return '<i class="icon-git"></i> ';
646 }
666 }
647 else if (repo_type === 'svn') {
667 else if (repo_type === 'svn') {
648 return '<i class="icon-svn"></i> ';
668 return '<i class="icon-svn"></i> ';
649 }
669 }
650 return ''
670 return ''
651 };
671 };
652
672
653 // highlight match
673 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
674
675 if (value.split(':').length === 2) {
676 value = value.split(':')[1]
677 }
678
679 var searchType = data['type'];
680 var valueDisplay = data['value_display'];
681
682 var pattern = '(' + escapeRegExChars(value) + ')';
683
654 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
684 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
685
686 // highlight match
687 if (searchType != 'text') {
655 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
688 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
689 }
656
690
657 var icon = '';
691 var icon = '';
658
692
659 if (searchType === 'hint') {
693 if (searchType === 'hint') {
660 icon += '<i class="icon-folder-close"></i> ';
694 icon += '<i class="icon-folder-close"></i> ';
661 }
695 }
662 // full text search
696 // full text search
663 else if (searchType === 'search') {
697 else if (searchType === 'search') {
664 icon += '<i class="icon-more"></i> ';
698 icon += '<i class="icon-more"></i> ';
665 }
699 }
666 // repository
700 // repository
667 else if (searchType === 'repo') {
701 else if (searchType === 'repo') {
668
702
669 var repoIcon = getRepoIcon(data['repo_type']);
703 var repoIcon = getRepoIcon(data['repo_type']);
670 icon += repoIcon;
704 icon += repoIcon;
671
705
672 if (data['private']) {
706 if (data['private']) {
673 icon += '<i class="icon-lock" ></i> ';
707 icon += '<i class="icon-lock" ></i> ';
674 }
708 }
675 else if (visualShowPublicIcon) {
709 else if (visualShowPublicIcon) {
676 icon += '<i class="icon-unlock-alt"></i> ';
710 icon += '<i class="icon-unlock-alt"></i> ';
677 }
711 }
678 }
712 }
679 // repository groups
713 // repository groups
680 else if (searchType === 'repo_group') {
714 else if (searchType === 'repo_group') {
681 icon += '<i class="icon-folder-close"></i> ';
715 icon += '<i class="icon-folder-close"></i> ';
682 }
716 }
683 // user group
717 // user group
684 else if (searchType === 'user_group') {
718 else if (searchType === 'user_group') {
685 icon += '<i class="icon-group"></i> ';
719 icon += '<i class="icon-group"></i> ';
686 }
720 }
721 // user
687 else if (searchType === 'user') {
722 else if (searchType === 'user') {
688 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
723 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
689 }
724 }
690 // commit
725 // commit
691 else if (searchType === 'commit') {
726 else if (searchType === 'commit') {
692 var repo_data = data['repo_data'];
727 var repo_data = data['repo_data'];
693 var repoIcon = getRepoIcon(repo_data['repository_type']);
728 var repoIcon = getRepoIcon(repo_data['repository_type']);
694 if (repoIcon) {
729 if (repoIcon) {
695 icon += repoIcon;
730 icon += repoIcon;
696 } else {
731 } else {
697 icon += '<i class="icon-tag"></i>';
732 icon += '<i class="icon-tag"></i>';
698 }
733 }
699 }
734 }
700 // file
735 // file
701 else if (searchType === 'file') {
736 else if (searchType === 'file') {
702 var repo_data = data['repo_data'];
737 var repo_data = data['repo_data'];
703 var repoIcon = getRepoIcon(repo_data['repository_type']);
738 var repoIcon = getRepoIcon(repo_data['repository_type']);
704 if (repoIcon) {
739 if (repoIcon) {
705 icon += repoIcon;
740 icon += repoIcon;
706 } else {
741 } else {
707 icon += '<i class="icon-tag"></i>';
742 icon += '<i class="icon-tag"></i>';
708 }
743 }
709 }
744 }
745 // generic text
746 else if (searchType === 'text') {
747 icon = '';
748 }
710
749
711 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
750 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
712 return tmpl.format(icon, valueDisplay);
751 return tmpl.format(icon, valueDisplay);
713 };
752 };
714
753
715 var handleSelect = function(element, suggestion) {
754 var handleSelect = function(element, suggestion) {
716 if (suggestion.type === "hint") {
755 if (suggestion.type === "hint") {
717 // we skip action
756 // we skip action
718 $('#main_filter').focus();
757 $('#main_filter').focus();
758 }
759 else if (suggestion.type === "text") {
760 // we skip action
761 $('#main_filter').focus();
762
719 } else {
763 } else {
720 window.location = suggestion['url'];
764 window.location = suggestion['url'];
721 }
765 }
722 };
766 };
767
723 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
768 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
724 if (queryLowerCase.split(':').length === 2) {
769 if (queryLowerCase.split(':').length === 2) {
725 queryLowerCase = queryLowerCase.split(':')[1]
770 queryLowerCase = queryLowerCase.split(':')[1]
726 }
771 }
772 if (suggestion.type === "text") {
773 // special case we don't want to "skip" display for
774 return true
775 }
727 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
776 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
728 };
777 };
729
778
779 var cleanContext = {
780 repo_view_type: null,
781
782 repo_id: null,
783 repo_name: "",
784
785 repo_group_id: null,
786 repo_group_name: null
787 };
788 var removeGoToFilter = function () {
789 $('.searchTagHidable').hide();
790 $('#main_filter').autocomplete(
791 'setOptions', {params:{search_context: cleanContext}});
792 };
793
730 $('#main_filter').autocomplete({
794 $('#main_filter').autocomplete({
731 serviceUrl: pyroutes.url('goto_switcher_data'),
795 serviceUrl: pyroutes.url('goto_switcher_data'),
732 params: {"search_context": templateContext.search_context},
796 params: {
797 "search_context": templateContext.search_context
798 },
733 minChars:2,
799 minChars:2,
734 maxHeight:400,
800 maxHeight:400,
735 deferRequestBy: 300, //miliseconds
801 deferRequestBy: 300, //miliseconds
736 tabDisabled: true,
802 tabDisabled: true,
737 autoSelectFirst: true,
803 autoSelectFirst: false,
738 formatResult: autocompleteMainFilterFormatResult,
804 formatResult: autocompleteMainFilterFormatResult,
739 lookupFilter: autocompleteMainFilterResult,
805 lookupFilter: autocompleteMainFilterResult,
740 onSelect: function (element, suggestion) {
806 onSelect: function (element, suggestion) {
741 handleSelect(element, suggestion);
807 handleSelect(element, suggestion);
742 return false;
808 return false;
743 },
809 },
744 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
810 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
745 if (jqXHR !== 'abort') {
811 if (jqXHR !== 'abort') {
746 alert("Error during search.\nError code: {0}".format(textStatus));
812 alert("Error during search.\nError code: {0}".format(textStatus));
747 window.location = '';
813 window.location = '';
748 }
814 }
749 }
815 }
750 });
816 });
751
817
752 showMainFilterBox = function () {
818 showMainFilterBox = function () {
753 $('#main_filter_help').toggle();
819 $('#main_filter_help').toggle();
754 };
820 };
755
821
822 $('#main_filter').on('keydown.autocomplete', function (e) {
823
824 var BACKSPACE = 8;
825 var el = $(e.currentTarget);
826 if(e.which === BACKSPACE){
827 var inputVal = el.val();
828 if (inputVal === ""){
829 removeGoToFilter()
830 }
831 }
832 });
833
756 </script>
834 </script>
757 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
835 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
758 </%def>
836 </%def>
759
837
760 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
838 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
761 <div class="modal-dialog">
839 <div class="modal-dialog">
762 <div class="modal-content">
840 <div class="modal-content">
763 <div class="modal-header">
841 <div class="modal-header">
764 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
842 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
765 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
843 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
766 </div>
844 </div>
767 <div class="modal-body">
845 <div class="modal-body">
768 <div class="block-left">
846 <div class="block-left">
769 <table class="keyboard-mappings">
847 <table class="keyboard-mappings">
770 <tbody>
848 <tbody>
771 <tr>
849 <tr>
772 <th></th>
850 <th></th>
773 <th>${_('Site-wide shortcuts')}</th>
851 <th>${_('Site-wide shortcuts')}</th>
774 </tr>
852 </tr>
775 <%
853 <%
776 elems = [
854 elems = [
777 ('/', 'Use quick search box'),
855 ('/', 'Use quick search box'),
778 ('g h', 'Goto home page'),
856 ('g h', 'Goto home page'),
779 ('g g', 'Goto my private gists page'),
857 ('g g', 'Goto my private gists page'),
780 ('g G', 'Goto my public gists page'),
858 ('g G', 'Goto my public gists page'),
781 ('g 0-9', 'Goto bookmarked items from 0-9'),
859 ('g 0-9', 'Goto bookmarked items from 0-9'),
782 ('n r', 'New repository page'),
860 ('n r', 'New repository page'),
783 ('n g', 'New gist page'),
861 ('n g', 'New gist page'),
784 ]
862 ]
785 %>
863 %>
786 %for key, desc in elems:
864 %for key, desc in elems:
787 <tr>
865 <tr>
788 <td class="keys">
866 <td class="keys">
789 <span class="key tag">${key}</span>
867 <span class="key tag">${key}</span>
790 </td>
868 </td>
791 <td>${desc}</td>
869 <td>${desc}</td>
792 </tr>
870 </tr>
793 %endfor
871 %endfor
794 </tbody>
872 </tbody>
795 </table>
873 </table>
796 </div>
874 </div>
797 <div class="block-left">
875 <div class="block-left">
798 <table class="keyboard-mappings">
876 <table class="keyboard-mappings">
799 <tbody>
877 <tbody>
800 <tr>
878 <tr>
801 <th></th>
879 <th></th>
802 <th>${_('Repositories')}</th>
880 <th>${_('Repositories')}</th>
803 </tr>
881 </tr>
804 <%
882 <%
805 elems = [
883 elems = [
806 ('g s', 'Goto summary page'),
884 ('g s', 'Goto summary page'),
807 ('g c', 'Goto changelog page'),
885 ('g c', 'Goto changelog page'),
808 ('g f', 'Goto files page'),
886 ('g f', 'Goto files page'),
809 ('g F', 'Goto files page with file search activated'),
887 ('g F', 'Goto files page with file search activated'),
810 ('g p', 'Goto pull requests page'),
888 ('g p', 'Goto pull requests page'),
811 ('g o', 'Goto repository settings'),
889 ('g o', 'Goto repository settings'),
812 ('g O', 'Goto repository permissions settings'),
890 ('g O', 'Goto repository permissions settings'),
813 ]
891 ]
814 %>
892 %>
815 %for key, desc in elems:
893 %for key, desc in elems:
816 <tr>
894 <tr>
817 <td class="keys">
895 <td class="keys">
818 <span class="key tag">${key}</span>
896 <span class="key tag">${key}</span>
819 </td>
897 </td>
820 <td>${desc}</td>
898 <td>${desc}</td>
821 </tr>
899 </tr>
822 %endfor
900 %endfor
823 </tbody>
901 </tbody>
824 </table>
902 </table>
825 </div>
903 </div>
826 </div>
904 </div>
827 <div class="modal-footer">
905 <div class="modal-footer">
828 </div>
906 </div>
829 </div><!-- /.modal-content -->
907 </div><!-- /.modal-content -->
830 </div><!-- /.modal-dialog -->
908 </div><!-- /.modal-dialog -->
831 </div><!-- /.modal -->
909 </div><!-- /.modal -->
832
910
@@ -1,158 +1,159 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html>
2 <!DOCTYPE html>
3
3
4 <%
4 <%
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
6 go_import_header = ''
6 go_import_header = ''
7 if hasattr(c, 'rhodecode_db_repo'):
7 if hasattr(c, 'rhodecode_db_repo'):
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
10 ## check repo context
10 c.template_context['repo_id'] = c.rhodecode_db_repo.repo_id
11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
12
12
13 if getattr(c, 'repo_group', None):
13 if getattr(c, 'repo_group', None):
14 c.template_context['repo_group_id'] = c.repo_group.group_id
14 c.template_context['repo_group_id'] = c.repo_group.group_id
15 c.template_context['repo_group_name'] = c.repo_group.group_name
15 c.template_context['repo_group_name'] = c.repo_group.group_name
16
16
17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
18 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
18 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
19 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
19 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
20 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
20 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
21 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
21 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
22 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
22 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
23
23
24 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
24 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
25 c.template_context['default_user'] = {
25 c.template_context['default_user'] = {
26 'username': h.DEFAULT_USER,
26 'username': h.DEFAULT_USER,
27 'user_id': 1
27 'user_id': 1
28 }
28 }
29 c.template_context['search_context'] = {
29 c.template_context['search_context'] = {
30 'repo_group_id': c.template_context.get('repo_group_id'),
30 'repo_group_id': c.template_context.get('repo_group_id'),
31 'repo_group_name': c.template_context.get('repo_group_name'),
31 'repo_group_name': c.template_context.get('repo_group_name'),
32 'repo_id': c.template_context.get('repo_id'),
32 'repo_name': c.template_context.get('repo_name'),
33 'repo_name': c.template_context.get('repo_name'),
33 'repo_view_type': c.template_context.get('repo_view_type'),
34 'repo_view_type': c.template_context.get('repo_view_type'),
34 }
35 }
35
36
36 %>
37 %>
37 <html xmlns="http://www.w3.org/1999/xhtml">
38 <html xmlns="http://www.w3.org/1999/xhtml">
38 <head>
39 <head>
39 <title>${self.title()}</title>
40 <title>${self.title()}</title>
40 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
41 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
41
42
42 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
43 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
43
44
44 % if 'safari' in (request.user_agent or '').lower():
45 % if 'safari' in (request.user_agent or '').lower():
45 <meta name="referrer" content="origin">
46 <meta name="referrer" content="origin">
46 % else:
47 % else:
47 <meta name="referrer" content="origin-when-cross-origin">
48 <meta name="referrer" content="origin-when-cross-origin">
48 % endif
49 % endif
49
50
50 <%def name="robots()">
51 <%def name="robots()">
51 <meta name="robots" content="index, nofollow"/>
52 <meta name="robots" content="index, nofollow"/>
52 </%def>
53 </%def>
53 ${self.robots()}
54 ${self.robots()}
54 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
55 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
55 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
56 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
56 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
57 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
57
58
58 ## CSS definitions
59 ## CSS definitions
59 <%def name="css()">
60 <%def name="css()">
60 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
61 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
61 ## EXTRA FOR CSS
62 ## EXTRA FOR CSS
62 ${self.css_extra()}
63 ${self.css_extra()}
63 </%def>
64 </%def>
64 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
65 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
65 <%def name="css_extra()">
66 <%def name="css_extra()">
66 </%def>
67 </%def>
67
68
68 ${self.css()}
69 ${self.css()}
69
70
70 ## JAVASCRIPT
71 ## JAVASCRIPT
71 <%def name="js()">
72 <%def name="js()">
72
73
73 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
74 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
74 <script type="text/javascript">
75 <script type="text/javascript">
75 // register templateContext to pass template variables to JS
76 // register templateContext to pass template variables to JS
76 var templateContext = ${h.json.dumps(c.template_context)|n};
77 var templateContext = ${h.json.dumps(c.template_context)|n};
77
78
78 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
79 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
79 var APPLICATION_PLUGINS = [];
80 var APPLICATION_PLUGINS = [];
80 var ASSET_URL = "${h.asset('')}";
81 var ASSET_URL = "${h.asset('')}";
81 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
82 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
82 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
83 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
83
84
84 var APPENLIGHT = {
85 var APPENLIGHT = {
85 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
86 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
86 key: '${getattr(c, "appenlight_api_public_key", "")}',
87 key: '${getattr(c, "appenlight_api_public_key", "")}',
87 % if getattr(c, 'appenlight_server_url', None):
88 % if getattr(c, 'appenlight_server_url', None):
88 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
89 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
89 % endif
90 % endif
90 requestInfo: {
91 requestInfo: {
91 % if getattr(c, 'rhodecode_user', None):
92 % if getattr(c, 'rhodecode_user', None):
92 ip: '${c.rhodecode_user.ip_addr}',
93 ip: '${c.rhodecode_user.ip_addr}',
93 username: '${c.rhodecode_user.username}'
94 username: '${c.rhodecode_user.username}'
94 % endif
95 % endif
95 },
96 },
96 tags: {
97 tags: {
97 rhodecode_version: '${c.rhodecode_version}',
98 rhodecode_version: '${c.rhodecode_version}',
98 rhodecode_edition: '${c.rhodecode_edition}'
99 rhodecode_edition: '${c.rhodecode_edition}'
99 }
100 }
100 };
101 };
101
102
102 </script>
103 </script>
103 <%include file="/base/plugins_base.mako"/>
104 <%include file="/base/plugins_base.mako"/>
104 <!--[if lt IE 9]>
105 <!--[if lt IE 9]>
105 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
106 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
106 <![endif]-->
107 <![endif]-->
107 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
108 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
108 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
109 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
109 ## avoide escaping the %N
110 ## avoide escaping the %N
110 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
111 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
111 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
112 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
112
113
113
114
114 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
115 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
115 ${self.js_extra()}
116 ${self.js_extra()}
116
117
117 <script type="text/javascript">
118 <script type="text/javascript">
118 Rhodecode = (function() {
119 Rhodecode = (function() {
119 function _Rhodecode() {
120 function _Rhodecode() {
120 this.comments = new CommentsController();
121 this.comments = new CommentsController();
121 }
122 }
122 return new _Rhodecode();
123 return new _Rhodecode();
123 })();
124 })();
124
125
125 $(document).ready(function(){
126 $(document).ready(function(){
126 show_more_event();
127 show_more_event();
127 timeagoActivate();
128 timeagoActivate();
128 clipboardActivate();
129 clipboardActivate();
129 })
130 })
130 </script>
131 </script>
131
132
132 </%def>
133 </%def>
133
134
134 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
135 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
135 <%def name="js_extra()"></%def>
136 <%def name="js_extra()"></%def>
136 ${self.js()}
137 ${self.js()}
137
138
138 <%def name="head_extra()"></%def>
139 <%def name="head_extra()"></%def>
139 ${self.head_extra()}
140 ${self.head_extra()}
140 ## extra stuff
141 ## extra stuff
141 %if c.pre_code:
142 %if c.pre_code:
142 ${c.pre_code|n}
143 ${c.pre_code|n}
143 %endif
144 %endif
144 </head>
145 </head>
145 <body id="body">
146 <body id="body">
146 <noscript>
147 <noscript>
147 <div class="noscript-error">
148 <div class="noscript-error">
148 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
149 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
149 </div>
150 </div>
150 </noscript>
151 </noscript>
151
152
152 ${next.body()}
153 ${next.body()}
153 %if c.post_code:
154 %if c.post_code:
154 ${c.post_code|n}
155 ${c.post_code|n}
155 %endif
156 %endif
156 <rhodecode-app></rhodecode-app>
157 <rhodecode-app></rhodecode-app>
157 </body>
158 </body>
158 </html>
159 </html>
@@ -1,222 +1,222 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 %if c.repo_name:
5 %if c.repo_name:
6 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
6 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
7 %elif c.repo_group_name:
7 %elif c.repo_group_name:
8 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
8 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
9 %else:
9 %else:
10 ${_('Search inside all accessible repositories')}
10 ${_('Search inside all accessible repositories')}
11 %endif
11 %endif
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 &middot; ${h.branding(c.rhodecode_name)}
13 &middot; ${h.branding(c.rhodecode_name)}
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <%def name="breadcrumbs_links()">
17 <%def name="breadcrumbs_links()">
18 %if c.repo_name:
18 %if c.repo_name:
19 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
19 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
20 %elif c.repo_group_name:
20 %elif c.repo_group_name:
21 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
21 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
22 %else:
22 %else:
23 ${_('Search inside all accessible repositories')}
23 ${_('Search inside all accessible repositories')}
24 %endif
24 %endif
25
25
26 </%def>
26 </%def>
27
27
28 <%def name="menu_bar_nav()">
28 <%def name="menu_bar_nav()">
29 %if c.repo_name:
29 %if c.repo_name:
30 ${self.menu_items(active='search')}
30 ${self.menu_items(active='search')}
31 %elif c.repo_group_name:
31 %elif c.repo_group_name:
32 ${self.menu_items(active='search')}
32 ${self.menu_items(active='search')}
33 %else:
33 %else:
34 ${self.menu_items(active='search')}
34 ${self.menu_items(active='search')}
35 %endif
35 %endif
36 </%def>
36 </%def>
37
37
38 <%def name="menu_bar_subnav()">
38 <%def name="menu_bar_subnav()">
39 %if c.repo_name:
39 %if c.repo_name:
40 ${self.repo_menu(active='search')}
40 ${self.repo_menu(active='summary')}
41 %elif c.repo_group_name:
41 %elif c.repo_group_name:
42 ${self.repo_group_menu(active='search')}
42 ${self.repo_group_menu(active='home')}
43 %endif
43 %endif
44 </%def>
44 </%def>
45
45
46 <%def name="repo_icon(db_repo)">
46 <%def name="repo_icon(db_repo)">
47 %if h.is_hg(db_repo):
47 %if h.is_hg(db_repo):
48 <i class="icon-hg"></i>
48 <i class="icon-hg"></i>
49 %endif
49 %endif
50 %if h.is_git(db_repo):
50 %if h.is_git(db_repo):
51 <i class="icon-git"></i>
51 <i class="icon-git"></i>
52 %endif
52 %endif
53 %if h.is_svn(db_repo):
53 %if h.is_svn(db_repo):
54 <i class="icon-svn"></i>
54 <i class="icon-svn"></i>
55 %endif
55 %endif
56 </%def>
56 </%def>
57
57
58 <%def name="repo_group_icon()">
58 <%def name="repo_group_icon()">
59 <i class="icon-folder-close"></i>
59 <i class="icon-folder-close"></i>
60 </%def>
60 </%def>
61
61
62 <%def name="main()">
62 <%def name="main()">
63 <div class="box">
63 <div class="box">
64 %if c.repo_name:
64 %if c.repo_name:
65 <!-- box / title -->
65 <!-- box / title -->
66 <div class="title">
66 <div class="title">
67 ${self.repo_page_title(c.rhodecode_db_repo)}
67 ${self.repo_page_title(c.rhodecode_db_repo)}
68 </div>
68 </div>
69 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
69 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
70 %elif c.repo_group_name:
70 %elif c.repo_group_name:
71 <!-- box / title -->
71 <!-- box / title -->
72 <div class="title">
72 <div class="title">
73 ${self.repo_group_page_title(c.repo_group)}
73 ${self.repo_group_page_title(c.repo_group)}
74 </div>
74 </div>
75 ${h.form(h.route_path('search_repo_group',repo_group_name=c.repo_group_name),method='get')}
75 ${h.form(h.route_path('search_repo_group',repo_group_name=c.repo_group_name),method='get')}
76 %else:
76 %else:
77 <!-- box / title -->
77 <!-- box / title -->
78 <div class="title">
78 <div class="title">
79 ${self.breadcrumbs()}
79 ${self.breadcrumbs()}
80 <ul class="links">&nbsp;</ul>
80 <ul class="links">&nbsp;</ul>
81 </div>
81 </div>
82 <!-- end box / title -->
82 <!-- end box / title -->
83 ${h.form(h.route_path('search'), method='get')}
83 ${h.form(h.route_path('search'), method='get')}
84 %endif
84 %endif
85 <div class="form search-form">
85 <div class="form search-form">
86 <div class="fields">
86 <div class="fields">
87
87
88 ${h.text('q', c.cur_query, placeholder="Enter query...")}
88 ${h.text('q', c.cur_query, placeholder="Enter query...")}
89
89
90 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
90 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
91 ${h.hidden('max_lines', '10')}
91 ${h.hidden('max_lines', '10')}
92
92
93 <input type="submit" value="${_('Search')}" class="btn"/>
93 <input type="submit" value="${_('Search')}" class="btn"/>
94 <br/>
94 <br/>
95
95
96 <div class="search-tags">
96 <div class="search-tags">
97 <span class="tag tag8">
97 <span class="tag tag8">
98 %if c.repo_name:
98 %if c.repo_name:
99 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
99 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
100 %elif c.repo_group_name:
100 %elif c.repo_group_name:
101 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
101 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
102 % else:
102 % else:
103 ${_('Global Search')}
103 ${_('Global Search')}
104 %endif
104 %endif
105 </span>
105 </span>
106
106
107 %if c.repo_name:
107 %if c.repo_name:
108 Β»
108 Β»
109 <span class="tag tag8">
109 <span class="tag tag8">
110 ${repo_icon(c.rhodecode_db_repo)}
110 ${repo_icon(c.rhodecode_db_repo)}
111 ${c.repo_name}
111 ${c.repo_name}
112 </span>
112 </span>
113
113
114 %elif c.repo_group_name:
114 %elif c.repo_group_name:
115 Β»
115 Β»
116 <span class="tag tag8">
116 <span class="tag tag8">
117 ${repo_group_icon()}
117 ${repo_group_icon()}
118 ${c.repo_group_name}
118 ${c.repo_group_name}
119 </span>
119 </span>
120 %endif
120 %endif
121
121
122
122
123 % for search_tag in c.search_tags:
123 % for search_tag in c.search_tags:
124 <br/><span class="tag disabled" style="margin-top: 3px">${search_tag}</span>
124 <br/><span class="tag disabled" style="margin-top: 3px">${search_tag}</span>
125 % endfor
125 % endfor
126
126
127 </div>
127 </div>
128
128
129 <div class="search-feedback-items">
129 <div class="search-feedback-items">
130 % for error in c.errors:
130 % for error in c.errors:
131 <span class="error-message">
131 <span class="error-message">
132 % for k,v in error.asdict().items():
132 % for k,v in error.asdict().items():
133 ${k} - ${v}
133 ${k} - ${v}
134 % endfor
134 % endfor
135 </span>
135 </span>
136 % endfor
136 % endfor
137 <div class="field">
137 <div class="field">
138 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Langague examples')}</p>
138 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Langague examples')}</p>
139 <pre id="search-help" style="display: none">\
139 <pre id="search-help" style="display: none">\
140
140
141 % if c.searcher.name == 'whoosh':
141 % if c.searcher.name == 'whoosh':
142 Example filter terms for `Whoosh` search:
142 Example filter terms for `Whoosh` search:
143 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
143 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
144 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
144 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
145
145
146 Generate wildcards using '*' character:
146 Generate wildcards using '*' character:
147 "repo_name:vcs*" - search everything starting with 'vcs'
147 "repo_name:vcs*" - search everything starting with 'vcs'
148 "repo_name:*vcs*" - search for repository containing 'vcs'
148 "repo_name:*vcs*" - search for repository containing 'vcs'
149
149
150 Optional AND / OR operators in queries
150 Optional AND / OR operators in queries
151 "repo_name:vcs OR repo_name:test"
151 "repo_name:vcs OR repo_name:test"
152 "owner:test AND repo_name:test*" AND extension:py
152 "owner:test AND repo_name:test*" AND extension:py
153
153
154 Move advanced search is available via ElasticSearch6 backend in EE edition.
154 Move advanced search is available via ElasticSearch6 backend in EE edition.
155 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
155 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
156 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
156 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
157 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
157 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
158
158
159 search type: content (File Content)
159 search type: content (File Content)
160 indexed fields: content
160 indexed fields: content
161
161
162 # search for `fix` string in all files
162 # search for `fix` string in all files
163 fix
163 fix
164
164
165 search type: commit (Commit message)
165 search type: commit (Commit message)
166 indexed fields: message
166 indexed fields: message
167
167
168 search type: path (File name)
168 search type: path (File name)
169 indexed fields: path
169 indexed fields: path
170
170
171 % else:
171 % else:
172 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
172 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
173 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
173 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
174 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
174 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
175 % for handler in c.searcher.get_handlers().values():
175 % for handler in c.searcher.get_handlers().values():
176
176
177 search type: ${handler.search_type_label}
177 search type: ${handler.search_type_label}
178 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
178 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
179 % for entry in handler.es_6_example_queries:
179 % for entry in handler.es_6_example_queries:
180 ${entry.rstrip()}
180 ${entry.rstrip()}
181 % endfor
181 % endfor
182 % endfor
182 % endfor
183
183
184 % endif
184 % endif
185 </pre>
185 </pre>
186 </div>
186 </div>
187
187
188 <div class="field">${c.runtime}</div>
188 <div class="field">${c.runtime}</div>
189 </div>
189 </div>
190 </div>
190 </div>
191 </div>
191 </div>
192
192
193 ${h.end_form()}
193 ${h.end_form()}
194 <div class="search">
194 <div class="search">
195 % if c.search_type == 'content':
195 % if c.search_type == 'content':
196 <%include file='search_content.mako'/>
196 <%include file='search_content.mako'/>
197 % elif c.search_type == 'path':
197 % elif c.search_type == 'path':
198 <%include file='search_path.mako'/>
198 <%include file='search_path.mako'/>
199 % elif c.search_type == 'commit':
199 % elif c.search_type == 'commit':
200 <%include file='search_commit.mako'/>
200 <%include file='search_commit.mako'/>
201 % elif c.search_type == 'repository':
201 % elif c.search_type == 'repository':
202 <%include file='search_repository.mako'/>
202 <%include file='search_repository.mako'/>
203 % endif
203 % endif
204 </div>
204 </div>
205 </div>
205 </div>
206 <script>
206 <script>
207 $(document).ready(function(){
207 $(document).ready(function(){
208 $("#id_search_type").select2({
208 $("#id_search_type").select2({
209 'containerCssClass': "drop-menu",
209 'containerCssClass': "drop-menu",
210 'dropdownCssClass': "drop-menu-dropdown",
210 'dropdownCssClass': "drop-menu-dropdown",
211 'dropdownAutoWidth': true,
211 'dropdownAutoWidth': true,
212 'minimumResultsForSearch': -1
212 'minimumResultsForSearch': -1
213 });
213 });
214
214
215 $('#q').autoGrowInput({maxWidth: 920});
215 $('#q').autoGrowInput({maxWidth: 920});
216
216
217 setTimeout(function() {
217 setTimeout(function() {
218 $('#q').keyup()
218 $('#q').keyup()
219 }, 1);
219 }, 1);
220 })
220 })
221 </script>
221 </script>
222 </%def>
222 </%def>
General Comments 0
You need to be logged in to leave comments. Login now