##// END OF EJS Templates
auth-tokens: expose all roles with explanation to help users understand it better.
marcink -
r4430:d880ce51 default
parent child Browse files
Show More
@@ -1,2041 +1,2041 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 import base64
27 import base64
28
28
29 import os
29 import os
30 import random
30 import random
31 import hashlib
31 import hashlib
32 import StringIO
32 import StringIO
33 import textwrap
33 import textwrap
34 import urllib
34 import urllib
35 import math
35 import math
36 import logging
36 import logging
37 import re
37 import re
38 import time
38 import time
39 import string
39 import string
40 import hashlib
40 import hashlib
41 from collections import OrderedDict
41 from collections import OrderedDict
42
42
43 import pygments
43 import pygments
44 import itertools
44 import itertools
45 import fnmatch
45 import fnmatch
46 import bleach
46 import bleach
47
47
48 from pyramid import compat
48 from pyramid import compat
49 from datetime import datetime
49 from datetime import datetime
50 from functools import partial
50 from functools import partial
51 from pygments.formatters.html import HtmlFormatter
51 from pygments.formatters.html import HtmlFormatter
52 from pygments.lexers import (
52 from pygments.lexers import (
53 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
54
54
55 from pyramid.threadlocal import get_current_request
55 from pyramid.threadlocal import get_current_request
56
56
57 from webhelpers2.html import literal, HTML, escape
57 from webhelpers2.html import literal, HTML, escape
58 from webhelpers2.html._autolink import _auto_link_urls
58 from webhelpers2.html._autolink import _auto_link_urls
59 from webhelpers2.html.tools import (
59 from webhelpers2.html.tools import (
60 button_to, highlight, js_obfuscate, strip_links, strip_tags)
60 button_to, highlight, js_obfuscate, strip_links, strip_tags)
61
61
62 from webhelpers2.text import (
62 from webhelpers2.text import (
63 chop_at, collapse, convert_accented_entities,
63 chop_at, collapse, convert_accented_entities,
64 convert_misc_entities, lchop, plural, rchop, remove_formatting,
64 convert_misc_entities, lchop, plural, rchop, remove_formatting,
65 replace_whitespace, urlify, truncate, wrap_paragraphs)
65 replace_whitespace, urlify, truncate, wrap_paragraphs)
66 from webhelpers2.date import time_ago_in_words
66 from webhelpers2.date import time_ago_in_words
67
67
68 from webhelpers2.html.tags import (
68 from webhelpers2.html.tags import (
69 _input, NotGiven, _make_safe_id_component as safeid,
69 _input, NotGiven, _make_safe_id_component as safeid,
70 form as insecure_form,
70 form as insecure_form,
71 auto_discovery_link, checkbox, end_form, file,
71 auto_discovery_link, checkbox, end_form, file,
72 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
72 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
73 select as raw_select, stylesheet_link, submit, text, password, textarea,
73 select as raw_select, stylesheet_link, submit, text, password, textarea,
74 ul, radio, Options)
74 ul, radio, Options)
75
75
76 from webhelpers2.number import format_byte_size
76 from webhelpers2.number import format_byte_size
77
77
78 from rhodecode.lib.action_parser import action_parser
78 from rhodecode.lib.action_parser import action_parser
79 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
79 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
80 from rhodecode.lib.ext_json import json
80 from rhodecode.lib.ext_json import json
81 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
81 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
82 from rhodecode.lib.utils2 import (
82 from rhodecode.lib.utils2 import (
83 str2bool, safe_unicode, safe_str,
83 str2bool, safe_unicode, safe_str,
84 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
84 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
85 AttributeDict, safe_int, md5, md5_safe, get_host_info)
85 AttributeDict, safe_int, md5, md5_safe, get_host_info)
86 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
86 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
87 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
87 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
88 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
88 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
89 from rhodecode.lib.index.search_utils import get_matching_line_offsets
89 from rhodecode.lib.index.search_utils import get_matching_line_offsets
90 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
90 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
91 from rhodecode.model.changeset_status import ChangesetStatusModel
91 from rhodecode.model.changeset_status import ChangesetStatusModel
92 from rhodecode.model.db import Permission, User, Repository
92 from rhodecode.model.db import Permission, User, Repository, UserApiKeys
93 from rhodecode.model.repo_group import RepoGroupModel
93 from rhodecode.model.repo_group import RepoGroupModel
94 from rhodecode.model.settings import IssueTrackerSettingsModel
94 from rhodecode.model.settings import IssueTrackerSettingsModel
95
95
96
96
97 log = logging.getLogger(__name__)
97 log = logging.getLogger(__name__)
98
98
99
99
100 DEFAULT_USER = User.DEFAULT_USER
100 DEFAULT_USER = User.DEFAULT_USER
101 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
101 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
102
102
103
103
104 def asset(path, ver=None, **kwargs):
104 def asset(path, ver=None, **kwargs):
105 """
105 """
106 Helper to generate a static asset file path for rhodecode assets
106 Helper to generate a static asset file path for rhodecode assets
107
107
108 eg. h.asset('images/image.png', ver='3923')
108 eg. h.asset('images/image.png', ver='3923')
109
109
110 :param path: path of asset
110 :param path: path of asset
111 :param ver: optional version query param to append as ?ver=
111 :param ver: optional version query param to append as ?ver=
112 """
112 """
113 request = get_current_request()
113 request = get_current_request()
114 query = {}
114 query = {}
115 query.update(kwargs)
115 query.update(kwargs)
116 if ver:
116 if ver:
117 query = {'ver': ver}
117 query = {'ver': ver}
118 return request.static_path(
118 return request.static_path(
119 'rhodecode:public/{}'.format(path), _query=query)
119 'rhodecode:public/{}'.format(path), _query=query)
120
120
121
121
122 default_html_escape_table = {
122 default_html_escape_table = {
123 ord('&'): u'&amp;',
123 ord('&'): u'&amp;',
124 ord('<'): u'&lt;',
124 ord('<'): u'&lt;',
125 ord('>'): u'&gt;',
125 ord('>'): u'&gt;',
126 ord('"'): u'&quot;',
126 ord('"'): u'&quot;',
127 ord("'"): u'&#39;',
127 ord("'"): u'&#39;',
128 }
128 }
129
129
130
130
131 def html_escape(text, html_escape_table=default_html_escape_table):
131 def html_escape(text, html_escape_table=default_html_escape_table):
132 """Produce entities within text."""
132 """Produce entities within text."""
133 return text.translate(html_escape_table)
133 return text.translate(html_escape_table)
134
134
135
135
136 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
136 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
137 """
137 """
138 Truncate string ``s`` at the first occurrence of ``sub``.
138 Truncate string ``s`` at the first occurrence of ``sub``.
139
139
140 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
140 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
141 """
141 """
142 suffix_if_chopped = suffix_if_chopped or ''
142 suffix_if_chopped = suffix_if_chopped or ''
143 pos = s.find(sub)
143 pos = s.find(sub)
144 if pos == -1:
144 if pos == -1:
145 return s
145 return s
146
146
147 if inclusive:
147 if inclusive:
148 pos += len(sub)
148 pos += len(sub)
149
149
150 chopped = s[:pos]
150 chopped = s[:pos]
151 left = s[pos:].strip()
151 left = s[pos:].strip()
152
152
153 if left and suffix_if_chopped:
153 if left and suffix_if_chopped:
154 chopped += suffix_if_chopped
154 chopped += suffix_if_chopped
155
155
156 return chopped
156 return chopped
157
157
158
158
159 def shorter(text, size=20, prefix=False):
159 def shorter(text, size=20, prefix=False):
160 postfix = '...'
160 postfix = '...'
161 if len(text) > size:
161 if len(text) > size:
162 if prefix:
162 if prefix:
163 # shorten in front
163 # shorten in front
164 return postfix + text[-(size - len(postfix)):]
164 return postfix + text[-(size - len(postfix)):]
165 else:
165 else:
166 return text[:size - len(postfix)] + postfix
166 return text[:size - len(postfix)] + postfix
167 return text
167 return text
168
168
169
169
170 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
170 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
171 """
171 """
172 Reset button
172 Reset button
173 """
173 """
174 return _input(type, name, value, id, attrs)
174 return _input(type, name, value, id, attrs)
175
175
176
176
177 def select(name, selected_values, options, id=NotGiven, **attrs):
177 def select(name, selected_values, options, id=NotGiven, **attrs):
178
178
179 if isinstance(options, (list, tuple)):
179 if isinstance(options, (list, tuple)):
180 options_iter = options
180 options_iter = options
181 # Handle old value,label lists ... where value also can be value,label lists
181 # Handle old value,label lists ... where value also can be value,label lists
182 options = Options()
182 options = Options()
183 for opt in options_iter:
183 for opt in options_iter:
184 if isinstance(opt, tuple) and len(opt) == 2:
184 if isinstance(opt, tuple) and len(opt) == 2:
185 value, label = opt
185 value, label = opt
186 elif isinstance(opt, basestring):
186 elif isinstance(opt, basestring):
187 value = label = opt
187 value = label = opt
188 else:
188 else:
189 raise ValueError('invalid select option type %r' % type(opt))
189 raise ValueError('invalid select option type %r' % type(opt))
190
190
191 if isinstance(value, (list, tuple)):
191 if isinstance(value, (list, tuple)):
192 option_group = options.add_optgroup(label)
192 option_group = options.add_optgroup(label)
193 for opt2 in value:
193 for opt2 in value:
194 if isinstance(opt2, tuple) and len(opt2) == 2:
194 if isinstance(opt2, tuple) and len(opt2) == 2:
195 group_value, group_label = opt2
195 group_value, group_label = opt2
196 elif isinstance(opt2, basestring):
196 elif isinstance(opt2, basestring):
197 group_value = group_label = opt2
197 group_value = group_label = opt2
198 else:
198 else:
199 raise ValueError('invalid select option type %r' % type(opt2))
199 raise ValueError('invalid select option type %r' % type(opt2))
200
200
201 option_group.add_option(group_label, group_value)
201 option_group.add_option(group_label, group_value)
202 else:
202 else:
203 options.add_option(label, value)
203 options.add_option(label, value)
204
204
205 return raw_select(name, selected_values, options, id=id, **attrs)
205 return raw_select(name, selected_values, options, id=id, **attrs)
206
206
207
207
208 def branding(name, length=40):
208 def branding(name, length=40):
209 return truncate(name, length, indicator="")
209 return truncate(name, length, indicator="")
210
210
211
211
212 def FID(raw_id, path):
212 def FID(raw_id, path):
213 """
213 """
214 Creates a unique ID for filenode based on it's hash of path and commit
214 Creates a unique ID for filenode based on it's hash of path and commit
215 it's safe to use in urls
215 it's safe to use in urls
216
216
217 :param raw_id:
217 :param raw_id:
218 :param path:
218 :param path:
219 """
219 """
220
220
221 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
221 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
222
222
223
223
224 class _GetError(object):
224 class _GetError(object):
225 """Get error from form_errors, and represent it as span wrapped error
225 """Get error from form_errors, and represent it as span wrapped error
226 message
226 message
227
227
228 :param field_name: field to fetch errors for
228 :param field_name: field to fetch errors for
229 :param form_errors: form errors dict
229 :param form_errors: form errors dict
230 """
230 """
231
231
232 def __call__(self, field_name, form_errors):
232 def __call__(self, field_name, form_errors):
233 tmpl = """<span class="error_msg">%s</span>"""
233 tmpl = """<span class="error_msg">%s</span>"""
234 if form_errors and field_name in form_errors:
234 if form_errors and field_name in form_errors:
235 return literal(tmpl % form_errors.get(field_name))
235 return literal(tmpl % form_errors.get(field_name))
236
236
237
237
238 get_error = _GetError()
238 get_error = _GetError()
239
239
240
240
241 class _ToolTip(object):
241 class _ToolTip(object):
242
242
243 def __call__(self, tooltip_title, trim_at=50):
243 def __call__(self, tooltip_title, trim_at=50):
244 """
244 """
245 Special function just to wrap our text into nice formatted
245 Special function just to wrap our text into nice formatted
246 autowrapped text
246 autowrapped text
247
247
248 :param tooltip_title:
248 :param tooltip_title:
249 """
249 """
250 tooltip_title = escape(tooltip_title)
250 tooltip_title = escape(tooltip_title)
251 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
251 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
252 return tooltip_title
252 return tooltip_title
253
253
254
254
255 tooltip = _ToolTip()
255 tooltip = _ToolTip()
256
256
257 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy file path"></i>'
257 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy file path"></i>'
258
258
259
259
260 def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_name=None, at_ref=None,
260 def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_name=None, at_ref=None,
261 limit_items=False, linkify_last_item=False, hide_last_item=False,
261 limit_items=False, linkify_last_item=False, hide_last_item=False,
262 copy_path_icon=True):
262 copy_path_icon=True):
263 if isinstance(file_path, str):
263 if isinstance(file_path, str):
264 file_path = safe_unicode(file_path)
264 file_path = safe_unicode(file_path)
265
265
266 if at_ref:
266 if at_ref:
267 route_qry = {'at': at_ref}
267 route_qry = {'at': at_ref}
268 default_landing_ref = at_ref or landing_ref_name or commit_id
268 default_landing_ref = at_ref or landing_ref_name or commit_id
269 else:
269 else:
270 route_qry = None
270 route_qry = None
271 default_landing_ref = commit_id
271 default_landing_ref = commit_id
272
272
273 # first segment is a `HOME` link to repo files root location
273 # first segment is a `HOME` link to repo files root location
274 root_name = literal(u'<i class="icon-home"></i>')
274 root_name = literal(u'<i class="icon-home"></i>')
275
275
276 url_segments = [
276 url_segments = [
277 link_to(
277 link_to(
278 root_name,
278 root_name,
279 repo_files_by_ref_url(
279 repo_files_by_ref_url(
280 repo_name,
280 repo_name,
281 repo_type,
281 repo_type,
282 f_path=None, # None here is a special case for SVN repos,
282 f_path=None, # None here is a special case for SVN repos,
283 # that won't prefix with a ref
283 # that won't prefix with a ref
284 ref_name=default_landing_ref,
284 ref_name=default_landing_ref,
285 commit_id=commit_id,
285 commit_id=commit_id,
286 query=route_qry
286 query=route_qry
287 )
287 )
288 )]
288 )]
289
289
290 path_segments = file_path.split('/')
290 path_segments = file_path.split('/')
291 last_cnt = len(path_segments) - 1
291 last_cnt = len(path_segments) - 1
292 for cnt, segment in enumerate(path_segments):
292 for cnt, segment in enumerate(path_segments):
293 if not segment:
293 if not segment:
294 continue
294 continue
295 segment_html = escape(segment)
295 segment_html = escape(segment)
296
296
297 last_item = cnt == last_cnt
297 last_item = cnt == last_cnt
298
298
299 if last_item and hide_last_item:
299 if last_item and hide_last_item:
300 # iterate over and hide last element
300 # iterate over and hide last element
301 continue
301 continue
302
302
303 if last_item and linkify_last_item is False:
303 if last_item and linkify_last_item is False:
304 # plain version
304 # plain version
305 url_segments.append(segment_html)
305 url_segments.append(segment_html)
306 else:
306 else:
307 url_segments.append(
307 url_segments.append(
308 link_to(
308 link_to(
309 segment_html,
309 segment_html,
310 repo_files_by_ref_url(
310 repo_files_by_ref_url(
311 repo_name,
311 repo_name,
312 repo_type,
312 repo_type,
313 f_path='/'.join(path_segments[:cnt + 1]),
313 f_path='/'.join(path_segments[:cnt + 1]),
314 ref_name=default_landing_ref,
314 ref_name=default_landing_ref,
315 commit_id=commit_id,
315 commit_id=commit_id,
316 query=route_qry
316 query=route_qry
317 ),
317 ),
318 ))
318 ))
319
319
320 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
320 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
321 if limit_items and len(limited_url_segments) < len(url_segments):
321 if limit_items and len(limited_url_segments) < len(url_segments):
322 url_segments = limited_url_segments
322 url_segments = limited_url_segments
323
323
324 full_path = file_path
324 full_path = file_path
325 if copy_path_icon:
325 if copy_path_icon:
326 icon = files_icon.format(escape(full_path))
326 icon = files_icon.format(escape(full_path))
327 else:
327 else:
328 icon = ''
328 icon = ''
329
329
330 if file_path == '':
330 if file_path == '':
331 return root_name
331 return root_name
332 else:
332 else:
333 return literal(' / '.join(url_segments) + icon)
333 return literal(' / '.join(url_segments) + icon)
334
334
335
335
336 def files_url_data(request):
336 def files_url_data(request):
337 matchdict = request.matchdict
337 matchdict = request.matchdict
338
338
339 if 'f_path' not in matchdict:
339 if 'f_path' not in matchdict:
340 matchdict['f_path'] = ''
340 matchdict['f_path'] = ''
341
341
342 if 'commit_id' not in matchdict:
342 if 'commit_id' not in matchdict:
343 matchdict['commit_id'] = 'tip'
343 matchdict['commit_id'] = 'tip'
344
344
345 return json.dumps(matchdict)
345 return json.dumps(matchdict)
346
346
347
347
348 def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_id, query=None, ):
348 def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_id, query=None, ):
349 _is_svn = is_svn(db_repo_type)
349 _is_svn = is_svn(db_repo_type)
350 final_f_path = f_path
350 final_f_path = f_path
351
351
352 if _is_svn:
352 if _is_svn:
353 """
353 """
354 For SVN the ref_name cannot be used as a commit_id, it needs to be prefixed with
354 For SVN the ref_name cannot be used as a commit_id, it needs to be prefixed with
355 actually commit_id followed by the ref_name. This should be done only in case
355 actually commit_id followed by the ref_name. This should be done only in case
356 This is a initial landing url, without additional paths.
356 This is a initial landing url, without additional paths.
357
357
358 like: /1000/tags/1.0.0/?at=tags/1.0.0
358 like: /1000/tags/1.0.0/?at=tags/1.0.0
359 """
359 """
360
360
361 if ref_name and ref_name != 'tip':
361 if ref_name and ref_name != 'tip':
362 # NOTE(marcink): for svn the ref_name is actually the stored path, so we prefix it
362 # NOTE(marcink): for svn the ref_name is actually the stored path, so we prefix it
363 # for SVN we only do this magic prefix if it's root, .eg landing revision
363 # for SVN we only do this magic prefix if it's root, .eg landing revision
364 # of files link. If we are in the tree we don't need this since we traverse the url
364 # of files link. If we are in the tree we don't need this since we traverse the url
365 # that has everything stored
365 # that has everything stored
366 if f_path in ['', '/']:
366 if f_path in ['', '/']:
367 final_f_path = '/'.join([ref_name, f_path])
367 final_f_path = '/'.join([ref_name, f_path])
368
368
369 # SVN always needs a commit_id explicitly, without a named REF
369 # SVN always needs a commit_id explicitly, without a named REF
370 default_commit_id = commit_id
370 default_commit_id = commit_id
371 else:
371 else:
372 """
372 """
373 For git and mercurial we construct a new URL using the names instead of commit_id
373 For git and mercurial we construct a new URL using the names instead of commit_id
374 like: /master/some_path?at=master
374 like: /master/some_path?at=master
375 """
375 """
376 # We currently do not support branches with slashes
376 # We currently do not support branches with slashes
377 if '/' in ref_name:
377 if '/' in ref_name:
378 default_commit_id = commit_id
378 default_commit_id = commit_id
379 else:
379 else:
380 default_commit_id = ref_name
380 default_commit_id = ref_name
381
381
382 # sometimes we pass f_path as None, to indicate explicit no prefix,
382 # sometimes we pass f_path as None, to indicate explicit no prefix,
383 # we translate it to string to not have None
383 # we translate it to string to not have None
384 final_f_path = final_f_path or ''
384 final_f_path = final_f_path or ''
385
385
386 files_url = route_path(
386 files_url = route_path(
387 'repo_files',
387 'repo_files',
388 repo_name=db_repo_name,
388 repo_name=db_repo_name,
389 commit_id=default_commit_id,
389 commit_id=default_commit_id,
390 f_path=final_f_path,
390 f_path=final_f_path,
391 _query=query
391 _query=query
392 )
392 )
393 return files_url
393 return files_url
394
394
395
395
396 def code_highlight(code, lexer, formatter, use_hl_filter=False):
396 def code_highlight(code, lexer, formatter, use_hl_filter=False):
397 """
397 """
398 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
398 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
399
399
400 If ``outfile`` is given and a valid file object (an object
400 If ``outfile`` is given and a valid file object (an object
401 with a ``write`` method), the result will be written to it, otherwise
401 with a ``write`` method), the result will be written to it, otherwise
402 it is returned as a string.
402 it is returned as a string.
403 """
403 """
404 if use_hl_filter:
404 if use_hl_filter:
405 # add HL filter
405 # add HL filter
406 from rhodecode.lib.index import search_utils
406 from rhodecode.lib.index import search_utils
407 lexer.add_filter(search_utils.ElasticSearchHLFilter())
407 lexer.add_filter(search_utils.ElasticSearchHLFilter())
408 return pygments.format(pygments.lex(code, lexer), formatter)
408 return pygments.format(pygments.lex(code, lexer), formatter)
409
409
410
410
411 class CodeHtmlFormatter(HtmlFormatter):
411 class CodeHtmlFormatter(HtmlFormatter):
412 """
412 """
413 My code Html Formatter for source codes
413 My code Html Formatter for source codes
414 """
414 """
415
415
416 def wrap(self, source, outfile):
416 def wrap(self, source, outfile):
417 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
417 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
418
418
419 def _wrap_code(self, source):
419 def _wrap_code(self, source):
420 for cnt, it in enumerate(source):
420 for cnt, it in enumerate(source):
421 i, t = it
421 i, t = it
422 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
422 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
423 yield i, t
423 yield i, t
424
424
425 def _wrap_tablelinenos(self, inner):
425 def _wrap_tablelinenos(self, inner):
426 dummyoutfile = StringIO.StringIO()
426 dummyoutfile = StringIO.StringIO()
427 lncount = 0
427 lncount = 0
428 for t, line in inner:
428 for t, line in inner:
429 if t:
429 if t:
430 lncount += 1
430 lncount += 1
431 dummyoutfile.write(line)
431 dummyoutfile.write(line)
432
432
433 fl = self.linenostart
433 fl = self.linenostart
434 mw = len(str(lncount + fl - 1))
434 mw = len(str(lncount + fl - 1))
435 sp = self.linenospecial
435 sp = self.linenospecial
436 st = self.linenostep
436 st = self.linenostep
437 la = self.lineanchors
437 la = self.lineanchors
438 aln = self.anchorlinenos
438 aln = self.anchorlinenos
439 nocls = self.noclasses
439 nocls = self.noclasses
440 if sp:
440 if sp:
441 lines = []
441 lines = []
442
442
443 for i in range(fl, fl + lncount):
443 for i in range(fl, fl + lncount):
444 if i % st == 0:
444 if i % st == 0:
445 if i % sp == 0:
445 if i % sp == 0:
446 if aln:
446 if aln:
447 lines.append('<a href="#%s%d" class="special">%*d</a>' %
447 lines.append('<a href="#%s%d" class="special">%*d</a>' %
448 (la, i, mw, i))
448 (la, i, mw, i))
449 else:
449 else:
450 lines.append('<span class="special">%*d</span>' % (mw, i))
450 lines.append('<span class="special">%*d</span>' % (mw, i))
451 else:
451 else:
452 if aln:
452 if aln:
453 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
453 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
454 else:
454 else:
455 lines.append('%*d' % (mw, i))
455 lines.append('%*d' % (mw, i))
456 else:
456 else:
457 lines.append('')
457 lines.append('')
458 ls = '\n'.join(lines)
458 ls = '\n'.join(lines)
459 else:
459 else:
460 lines = []
460 lines = []
461 for i in range(fl, fl + lncount):
461 for i in range(fl, fl + lncount):
462 if i % st == 0:
462 if i % st == 0:
463 if aln:
463 if aln:
464 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
464 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
465 else:
465 else:
466 lines.append('%*d' % (mw, i))
466 lines.append('%*d' % (mw, i))
467 else:
467 else:
468 lines.append('')
468 lines.append('')
469 ls = '\n'.join(lines)
469 ls = '\n'.join(lines)
470
470
471 # in case you wonder about the seemingly redundant <div> here: since the
471 # in case you wonder about the seemingly redundant <div> here: since the
472 # content in the other cell also is wrapped in a div, some browsers in
472 # content in the other cell also is wrapped in a div, some browsers in
473 # some configurations seem to mess up the formatting...
473 # some configurations seem to mess up the formatting...
474 if nocls:
474 if nocls:
475 yield 0, ('<table class="%stable">' % self.cssclass +
475 yield 0, ('<table class="%stable">' % self.cssclass +
476 '<tr><td><div class="linenodiv" '
476 '<tr><td><div class="linenodiv" '
477 'style="background-color: #f0f0f0; padding-right: 10px">'
477 'style="background-color: #f0f0f0; padding-right: 10px">'
478 '<pre style="line-height: 125%">' +
478 '<pre style="line-height: 125%">' +
479 ls + '</pre></div></td><td id="hlcode" class="code">')
479 ls + '</pre></div></td><td id="hlcode" class="code">')
480 else:
480 else:
481 yield 0, ('<table class="%stable">' % self.cssclass +
481 yield 0, ('<table class="%stable">' % self.cssclass +
482 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
482 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
483 ls + '</pre></div></td><td id="hlcode" class="code">')
483 ls + '</pre></div></td><td id="hlcode" class="code">')
484 yield 0, dummyoutfile.getvalue()
484 yield 0, dummyoutfile.getvalue()
485 yield 0, '</td></tr></table>'
485 yield 0, '</td></tr></table>'
486
486
487
487
488 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
488 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
489 def __init__(self, **kw):
489 def __init__(self, **kw):
490 # only show these line numbers if set
490 # only show these line numbers if set
491 self.only_lines = kw.pop('only_line_numbers', [])
491 self.only_lines = kw.pop('only_line_numbers', [])
492 self.query_terms = kw.pop('query_terms', [])
492 self.query_terms = kw.pop('query_terms', [])
493 self.max_lines = kw.pop('max_lines', 5)
493 self.max_lines = kw.pop('max_lines', 5)
494 self.line_context = kw.pop('line_context', 3)
494 self.line_context = kw.pop('line_context', 3)
495 self.url = kw.pop('url', None)
495 self.url = kw.pop('url', None)
496
496
497 super(CodeHtmlFormatter, self).__init__(**kw)
497 super(CodeHtmlFormatter, self).__init__(**kw)
498
498
499 def _wrap_code(self, source):
499 def _wrap_code(self, source):
500 for cnt, it in enumerate(source):
500 for cnt, it in enumerate(source):
501 i, t = it
501 i, t = it
502 t = '<pre>%s</pre>' % t
502 t = '<pre>%s</pre>' % t
503 yield i, t
503 yield i, t
504
504
505 def _wrap_tablelinenos(self, inner):
505 def _wrap_tablelinenos(self, inner):
506 yield 0, '<table class="code-highlight %stable">' % self.cssclass
506 yield 0, '<table class="code-highlight %stable">' % self.cssclass
507
507
508 last_shown_line_number = 0
508 last_shown_line_number = 0
509 current_line_number = 1
509 current_line_number = 1
510
510
511 for t, line in inner:
511 for t, line in inner:
512 if not t:
512 if not t:
513 yield t, line
513 yield t, line
514 continue
514 continue
515
515
516 if current_line_number in self.only_lines:
516 if current_line_number in self.only_lines:
517 if last_shown_line_number + 1 != current_line_number:
517 if last_shown_line_number + 1 != current_line_number:
518 yield 0, '<tr>'
518 yield 0, '<tr>'
519 yield 0, '<td class="line">...</td>'
519 yield 0, '<td class="line">...</td>'
520 yield 0, '<td id="hlcode" class="code"></td>'
520 yield 0, '<td id="hlcode" class="code"></td>'
521 yield 0, '</tr>'
521 yield 0, '</tr>'
522
522
523 yield 0, '<tr>'
523 yield 0, '<tr>'
524 if self.url:
524 if self.url:
525 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
525 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
526 self.url, current_line_number, current_line_number)
526 self.url, current_line_number, current_line_number)
527 else:
527 else:
528 yield 0, '<td class="line"><a href="">%i</a></td>' % (
528 yield 0, '<td class="line"><a href="">%i</a></td>' % (
529 current_line_number)
529 current_line_number)
530 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
530 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
531 yield 0, '</tr>'
531 yield 0, '</tr>'
532
532
533 last_shown_line_number = current_line_number
533 last_shown_line_number = current_line_number
534
534
535 current_line_number += 1
535 current_line_number += 1
536
536
537 yield 0, '</table>'
537 yield 0, '</table>'
538
538
539
539
540 def hsv_to_rgb(h, s, v):
540 def hsv_to_rgb(h, s, v):
541 """ Convert hsv color values to rgb """
541 """ Convert hsv color values to rgb """
542
542
543 if s == 0.0:
543 if s == 0.0:
544 return v, v, v
544 return v, v, v
545 i = int(h * 6.0) # XXX assume int() truncates!
545 i = int(h * 6.0) # XXX assume int() truncates!
546 f = (h * 6.0) - i
546 f = (h * 6.0) - i
547 p = v * (1.0 - s)
547 p = v * (1.0 - s)
548 q = v * (1.0 - s * f)
548 q = v * (1.0 - s * f)
549 t = v * (1.0 - s * (1.0 - f))
549 t = v * (1.0 - s * (1.0 - f))
550 i = i % 6
550 i = i % 6
551 if i == 0:
551 if i == 0:
552 return v, t, p
552 return v, t, p
553 if i == 1:
553 if i == 1:
554 return q, v, p
554 return q, v, p
555 if i == 2:
555 if i == 2:
556 return p, v, t
556 return p, v, t
557 if i == 3:
557 if i == 3:
558 return p, q, v
558 return p, q, v
559 if i == 4:
559 if i == 4:
560 return t, p, v
560 return t, p, v
561 if i == 5:
561 if i == 5:
562 return v, p, q
562 return v, p, q
563
563
564
564
565 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
565 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
566 """
566 """
567 Generator for getting n of evenly distributed colors using
567 Generator for getting n of evenly distributed colors using
568 hsv color and golden ratio. It always return same order of colors
568 hsv color and golden ratio. It always return same order of colors
569
569
570 :param n: number of colors to generate
570 :param n: number of colors to generate
571 :param saturation: saturation of returned colors
571 :param saturation: saturation of returned colors
572 :param lightness: lightness of returned colors
572 :param lightness: lightness of returned colors
573 :returns: RGB tuple
573 :returns: RGB tuple
574 """
574 """
575
575
576 golden_ratio = 0.618033988749895
576 golden_ratio = 0.618033988749895
577 h = 0.22717784590367374
577 h = 0.22717784590367374
578
578
579 for _ in xrange(n):
579 for _ in xrange(n):
580 h += golden_ratio
580 h += golden_ratio
581 h %= 1
581 h %= 1
582 HSV_tuple = [h, saturation, lightness]
582 HSV_tuple = [h, saturation, lightness]
583 RGB_tuple = hsv_to_rgb(*HSV_tuple)
583 RGB_tuple = hsv_to_rgb(*HSV_tuple)
584 yield map(lambda x: str(int(x * 256)), RGB_tuple)
584 yield map(lambda x: str(int(x * 256)), RGB_tuple)
585
585
586
586
587 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
587 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
588 """
588 """
589 Returns a function which when called with an argument returns a unique
589 Returns a function which when called with an argument returns a unique
590 color for that argument, eg.
590 color for that argument, eg.
591
591
592 :param n: number of colors to generate
592 :param n: number of colors to generate
593 :param saturation: saturation of returned colors
593 :param saturation: saturation of returned colors
594 :param lightness: lightness of returned colors
594 :param lightness: lightness of returned colors
595 :returns: css RGB string
595 :returns: css RGB string
596
596
597 >>> color_hash = color_hasher()
597 >>> color_hash = color_hasher()
598 >>> color_hash('hello')
598 >>> color_hash('hello')
599 'rgb(34, 12, 59)'
599 'rgb(34, 12, 59)'
600 >>> color_hash('hello')
600 >>> color_hash('hello')
601 'rgb(34, 12, 59)'
601 'rgb(34, 12, 59)'
602 >>> color_hash('other')
602 >>> color_hash('other')
603 'rgb(90, 224, 159)'
603 'rgb(90, 224, 159)'
604 """
604 """
605
605
606 color_dict = {}
606 color_dict = {}
607 cgenerator = unique_color_generator(
607 cgenerator = unique_color_generator(
608 saturation=saturation, lightness=lightness)
608 saturation=saturation, lightness=lightness)
609
609
610 def get_color_string(thing):
610 def get_color_string(thing):
611 if thing in color_dict:
611 if thing in color_dict:
612 col = color_dict[thing]
612 col = color_dict[thing]
613 else:
613 else:
614 col = color_dict[thing] = cgenerator.next()
614 col = color_dict[thing] = cgenerator.next()
615 return "rgb(%s)" % (', '.join(col))
615 return "rgb(%s)" % (', '.join(col))
616
616
617 return get_color_string
617 return get_color_string
618
618
619
619
620 def get_lexer_safe(mimetype=None, filepath=None):
620 def get_lexer_safe(mimetype=None, filepath=None):
621 """
621 """
622 Tries to return a relevant pygments lexer using mimetype/filepath name,
622 Tries to return a relevant pygments lexer using mimetype/filepath name,
623 defaulting to plain text if none could be found
623 defaulting to plain text if none could be found
624 """
624 """
625 lexer = None
625 lexer = None
626 try:
626 try:
627 if mimetype:
627 if mimetype:
628 lexer = get_lexer_for_mimetype(mimetype)
628 lexer = get_lexer_for_mimetype(mimetype)
629 if not lexer:
629 if not lexer:
630 lexer = get_lexer_for_filename(filepath)
630 lexer = get_lexer_for_filename(filepath)
631 except pygments.util.ClassNotFound:
631 except pygments.util.ClassNotFound:
632 pass
632 pass
633
633
634 if not lexer:
634 if not lexer:
635 lexer = get_lexer_by_name('text')
635 lexer = get_lexer_by_name('text')
636
636
637 return lexer
637 return lexer
638
638
639
639
640 def get_lexer_for_filenode(filenode):
640 def get_lexer_for_filenode(filenode):
641 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
641 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
642 return lexer
642 return lexer
643
643
644
644
645 def pygmentize(filenode, **kwargs):
645 def pygmentize(filenode, **kwargs):
646 """
646 """
647 pygmentize function using pygments
647 pygmentize function using pygments
648
648
649 :param filenode:
649 :param filenode:
650 """
650 """
651 lexer = get_lexer_for_filenode(filenode)
651 lexer = get_lexer_for_filenode(filenode)
652 return literal(code_highlight(filenode.content, lexer,
652 return literal(code_highlight(filenode.content, lexer,
653 CodeHtmlFormatter(**kwargs)))
653 CodeHtmlFormatter(**kwargs)))
654
654
655
655
656 def is_following_repo(repo_name, user_id):
656 def is_following_repo(repo_name, user_id):
657 from rhodecode.model.scm import ScmModel
657 from rhodecode.model.scm import ScmModel
658 return ScmModel().is_following_repo(repo_name, user_id)
658 return ScmModel().is_following_repo(repo_name, user_id)
659
659
660
660
661 class _Message(object):
661 class _Message(object):
662 """A message returned by ``Flash.pop_messages()``.
662 """A message returned by ``Flash.pop_messages()``.
663
663
664 Converting the message to a string returns the message text. Instances
664 Converting the message to a string returns the message text. Instances
665 also have the following attributes:
665 also have the following attributes:
666
666
667 * ``message``: the message text.
667 * ``message``: the message text.
668 * ``category``: the category specified when the message was created.
668 * ``category``: the category specified when the message was created.
669 """
669 """
670
670
671 def __init__(self, category, message, sub_data=None):
671 def __init__(self, category, message, sub_data=None):
672 self.category = category
672 self.category = category
673 self.message = message
673 self.message = message
674 self.sub_data = sub_data or {}
674 self.sub_data = sub_data or {}
675
675
676 def __str__(self):
676 def __str__(self):
677 return self.message
677 return self.message
678
678
679 __unicode__ = __str__
679 __unicode__ = __str__
680
680
681 def __html__(self):
681 def __html__(self):
682 return escape(safe_unicode(self.message))
682 return escape(safe_unicode(self.message))
683
683
684
684
685 class Flash(object):
685 class Flash(object):
686 # List of allowed categories. If None, allow any category.
686 # List of allowed categories. If None, allow any category.
687 categories = ["warning", "notice", "error", "success"]
687 categories = ["warning", "notice", "error", "success"]
688
688
689 # Default category if none is specified.
689 # Default category if none is specified.
690 default_category = "notice"
690 default_category = "notice"
691
691
692 def __init__(self, session_key="flash", categories=None,
692 def __init__(self, session_key="flash", categories=None,
693 default_category=None):
693 default_category=None):
694 """
694 """
695 Instantiate a ``Flash`` object.
695 Instantiate a ``Flash`` object.
696
696
697 ``session_key`` is the key to save the messages under in the user's
697 ``session_key`` is the key to save the messages under in the user's
698 session.
698 session.
699
699
700 ``categories`` is an optional list which overrides the default list
700 ``categories`` is an optional list which overrides the default list
701 of categories.
701 of categories.
702
702
703 ``default_category`` overrides the default category used for messages
703 ``default_category`` overrides the default category used for messages
704 when none is specified.
704 when none is specified.
705 """
705 """
706 self.session_key = session_key
706 self.session_key = session_key
707 if categories is not None:
707 if categories is not None:
708 self.categories = categories
708 self.categories = categories
709 if default_category is not None:
709 if default_category is not None:
710 self.default_category = default_category
710 self.default_category = default_category
711 if self.categories and self.default_category not in self.categories:
711 if self.categories and self.default_category not in self.categories:
712 raise ValueError(
712 raise ValueError(
713 "unrecognized default category %r" % (self.default_category,))
713 "unrecognized default category %r" % (self.default_category,))
714
714
715 def pop_messages(self, session=None, request=None):
715 def pop_messages(self, session=None, request=None):
716 """
716 """
717 Return all accumulated messages and delete them from the session.
717 Return all accumulated messages and delete them from the session.
718
718
719 The return value is a list of ``Message`` objects.
719 The return value is a list of ``Message`` objects.
720 """
720 """
721 messages = []
721 messages = []
722
722
723 if not session:
723 if not session:
724 if not request:
724 if not request:
725 request = get_current_request()
725 request = get_current_request()
726 session = request.session
726 session = request.session
727
727
728 # Pop the 'old' pylons flash messages. They are tuples of the form
728 # Pop the 'old' pylons flash messages. They are tuples of the form
729 # (category, message)
729 # (category, message)
730 for cat, msg in session.pop(self.session_key, []):
730 for cat, msg in session.pop(self.session_key, []):
731 messages.append(_Message(cat, msg))
731 messages.append(_Message(cat, msg))
732
732
733 # Pop the 'new' pyramid flash messages for each category as list
733 # Pop the 'new' pyramid flash messages for each category as list
734 # of strings.
734 # of strings.
735 for cat in self.categories:
735 for cat in self.categories:
736 for msg in session.pop_flash(queue=cat):
736 for msg in session.pop_flash(queue=cat):
737 sub_data = {}
737 sub_data = {}
738 if hasattr(msg, 'rsplit'):
738 if hasattr(msg, 'rsplit'):
739 flash_data = msg.rsplit('|DELIM|', 1)
739 flash_data = msg.rsplit('|DELIM|', 1)
740 org_message = flash_data[0]
740 org_message = flash_data[0]
741 if len(flash_data) > 1:
741 if len(flash_data) > 1:
742 sub_data = json.loads(flash_data[1])
742 sub_data = json.loads(flash_data[1])
743 else:
743 else:
744 org_message = msg
744 org_message = msg
745
745
746 messages.append(_Message(cat, org_message, sub_data=sub_data))
746 messages.append(_Message(cat, org_message, sub_data=sub_data))
747
747
748 # Map messages from the default queue to the 'notice' category.
748 # Map messages from the default queue to the 'notice' category.
749 for msg in session.pop_flash():
749 for msg in session.pop_flash():
750 messages.append(_Message('notice', msg))
750 messages.append(_Message('notice', msg))
751
751
752 session.save()
752 session.save()
753 return messages
753 return messages
754
754
755 def json_alerts(self, session=None, request=None):
755 def json_alerts(self, session=None, request=None):
756 payloads = []
756 payloads = []
757 messages = flash.pop_messages(session=session, request=request) or []
757 messages = flash.pop_messages(session=session, request=request) or []
758 for message in messages:
758 for message in messages:
759 payloads.append({
759 payloads.append({
760 'message': {
760 'message': {
761 'message': u'{}'.format(message.message),
761 'message': u'{}'.format(message.message),
762 'level': message.category,
762 'level': message.category,
763 'force': True,
763 'force': True,
764 'subdata': message.sub_data
764 'subdata': message.sub_data
765 }
765 }
766 })
766 })
767 return json.dumps(payloads)
767 return json.dumps(payloads)
768
768
769 def __call__(self, message, category=None, ignore_duplicate=True,
769 def __call__(self, message, category=None, ignore_duplicate=True,
770 session=None, request=None):
770 session=None, request=None):
771
771
772 if not session:
772 if not session:
773 if not request:
773 if not request:
774 request = get_current_request()
774 request = get_current_request()
775 session = request.session
775 session = request.session
776
776
777 session.flash(
777 session.flash(
778 message, queue=category, allow_duplicate=not ignore_duplicate)
778 message, queue=category, allow_duplicate=not ignore_duplicate)
779
779
780
780
781 flash = Flash()
781 flash = Flash()
782
782
783 #==============================================================================
783 #==============================================================================
784 # SCM FILTERS available via h.
784 # SCM FILTERS available via h.
785 #==============================================================================
785 #==============================================================================
786 from rhodecode.lib.vcs.utils import author_name, author_email
786 from rhodecode.lib.vcs.utils import author_name, author_email
787 from rhodecode.lib.utils2 import age, age_from_seconds
787 from rhodecode.lib.utils2 import age, age_from_seconds
788 from rhodecode.model.db import User, ChangesetStatus
788 from rhodecode.model.db import User, ChangesetStatus
789
789
790
790
791 email = author_email
791 email = author_email
792
792
793
793
794 def capitalize(raw_text):
794 def capitalize(raw_text):
795 return raw_text.capitalize()
795 return raw_text.capitalize()
796
796
797
797
798 def short_id(long_id):
798 def short_id(long_id):
799 return long_id[:12]
799 return long_id[:12]
800
800
801
801
802 def hide_credentials(url):
802 def hide_credentials(url):
803 from rhodecode.lib.utils2 import credentials_filter
803 from rhodecode.lib.utils2 import credentials_filter
804 return credentials_filter(url)
804 return credentials_filter(url)
805
805
806
806
807 import pytz
807 import pytz
808 import tzlocal
808 import tzlocal
809 local_timezone = tzlocal.get_localzone()
809 local_timezone = tzlocal.get_localzone()
810
810
811
811
812 def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
812 def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
813 title = value or format_date(datetime_iso)
813 title = value or format_date(datetime_iso)
814 tzinfo = '+00:00'
814 tzinfo = '+00:00'
815
815
816 # detect if we have a timezone info, otherwise, add it
816 # detect if we have a timezone info, otherwise, add it
817 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
817 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
818 force_timezone = os.environ.get('RC_TIMEZONE', '')
818 force_timezone = os.environ.get('RC_TIMEZONE', '')
819 if force_timezone:
819 if force_timezone:
820 force_timezone = pytz.timezone(force_timezone)
820 force_timezone = pytz.timezone(force_timezone)
821 timezone = force_timezone or local_timezone
821 timezone = force_timezone or local_timezone
822 offset = timezone.localize(datetime_iso).strftime('%z')
822 offset = timezone.localize(datetime_iso).strftime('%z')
823 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
823 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
824
824
825 return literal(
825 return literal(
826 '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
826 '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
827 cls='tooltip' if tooltip else '',
827 cls='tooltip' if tooltip else '',
828 tt_title=('{title}{tzinfo}'.format(title=title, tzinfo=tzinfo)) if tooltip else '',
828 tt_title=('{title}{tzinfo}'.format(title=title, tzinfo=tzinfo)) if tooltip else '',
829 title=title, dt=datetime_iso, tzinfo=tzinfo
829 title=title, dt=datetime_iso, tzinfo=tzinfo
830 ))
830 ))
831
831
832
832
833 def _shorten_commit_id(commit_id, commit_len=None):
833 def _shorten_commit_id(commit_id, commit_len=None):
834 if commit_len is None:
834 if commit_len is None:
835 request = get_current_request()
835 request = get_current_request()
836 commit_len = request.call_context.visual.show_sha_length
836 commit_len = request.call_context.visual.show_sha_length
837 return commit_id[:commit_len]
837 return commit_id[:commit_len]
838
838
839
839
840 def show_id(commit, show_idx=None, commit_len=None):
840 def show_id(commit, show_idx=None, commit_len=None):
841 """
841 """
842 Configurable function that shows ID
842 Configurable function that shows ID
843 by default it's r123:fffeeefffeee
843 by default it's r123:fffeeefffeee
844
844
845 :param commit: commit instance
845 :param commit: commit instance
846 """
846 """
847 if show_idx is None:
847 if show_idx is None:
848 request = get_current_request()
848 request = get_current_request()
849 show_idx = request.call_context.visual.show_revision_number
849 show_idx = request.call_context.visual.show_revision_number
850
850
851 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
851 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
852 if show_idx:
852 if show_idx:
853 return 'r%s:%s' % (commit.idx, raw_id)
853 return 'r%s:%s' % (commit.idx, raw_id)
854 else:
854 else:
855 return '%s' % (raw_id, )
855 return '%s' % (raw_id, )
856
856
857
857
858 def format_date(date):
858 def format_date(date):
859 """
859 """
860 use a standardized formatting for dates used in RhodeCode
860 use a standardized formatting for dates used in RhodeCode
861
861
862 :param date: date/datetime object
862 :param date: date/datetime object
863 :return: formatted date
863 :return: formatted date
864 """
864 """
865
865
866 if date:
866 if date:
867 _fmt = "%a, %d %b %Y %H:%M:%S"
867 _fmt = "%a, %d %b %Y %H:%M:%S"
868 return safe_unicode(date.strftime(_fmt))
868 return safe_unicode(date.strftime(_fmt))
869
869
870 return u""
870 return u""
871
871
872
872
873 class _RepoChecker(object):
873 class _RepoChecker(object):
874
874
875 def __init__(self, backend_alias):
875 def __init__(self, backend_alias):
876 self._backend_alias = backend_alias
876 self._backend_alias = backend_alias
877
877
878 def __call__(self, repository):
878 def __call__(self, repository):
879 if hasattr(repository, 'alias'):
879 if hasattr(repository, 'alias'):
880 _type = repository.alias
880 _type = repository.alias
881 elif hasattr(repository, 'repo_type'):
881 elif hasattr(repository, 'repo_type'):
882 _type = repository.repo_type
882 _type = repository.repo_type
883 else:
883 else:
884 _type = repository
884 _type = repository
885 return _type == self._backend_alias
885 return _type == self._backend_alias
886
886
887
887
888 is_git = _RepoChecker('git')
888 is_git = _RepoChecker('git')
889 is_hg = _RepoChecker('hg')
889 is_hg = _RepoChecker('hg')
890 is_svn = _RepoChecker('svn')
890 is_svn = _RepoChecker('svn')
891
891
892
892
893 def get_repo_type_by_name(repo_name):
893 def get_repo_type_by_name(repo_name):
894 repo = Repository.get_by_repo_name(repo_name)
894 repo = Repository.get_by_repo_name(repo_name)
895 if repo:
895 if repo:
896 return repo.repo_type
896 return repo.repo_type
897
897
898
898
899 def is_svn_without_proxy(repository):
899 def is_svn_without_proxy(repository):
900 if is_svn(repository):
900 if is_svn(repository):
901 from rhodecode.model.settings import VcsSettingsModel
901 from rhodecode.model.settings import VcsSettingsModel
902 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
902 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
903 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
903 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
904 return False
904 return False
905
905
906
906
907 def discover_user(author):
907 def discover_user(author):
908 """
908 """
909 Tries to discover RhodeCode User based on the author string. Author string
909 Tries to discover RhodeCode User based on the author string. Author string
910 is typically `FirstName LastName <email@address.com>`
910 is typically `FirstName LastName <email@address.com>`
911 """
911 """
912
912
913 # if author is already an instance use it for extraction
913 # if author is already an instance use it for extraction
914 if isinstance(author, User):
914 if isinstance(author, User):
915 return author
915 return author
916
916
917 # Valid email in the attribute passed, see if they're in the system
917 # Valid email in the attribute passed, see if they're in the system
918 _email = author_email(author)
918 _email = author_email(author)
919 if _email != '':
919 if _email != '':
920 user = User.get_by_email(_email, case_insensitive=True, cache=True)
920 user = User.get_by_email(_email, case_insensitive=True, cache=True)
921 if user is not None:
921 if user is not None:
922 return user
922 return user
923
923
924 # Maybe it's a username, we try to extract it and fetch by username ?
924 # Maybe it's a username, we try to extract it and fetch by username ?
925 _author = author_name(author)
925 _author = author_name(author)
926 user = User.get_by_username(_author, case_insensitive=True, cache=True)
926 user = User.get_by_username(_author, case_insensitive=True, cache=True)
927 if user is not None:
927 if user is not None:
928 return user
928 return user
929
929
930 return None
930 return None
931
931
932
932
933 def email_or_none(author):
933 def email_or_none(author):
934 # extract email from the commit string
934 # extract email from the commit string
935 _email = author_email(author)
935 _email = author_email(author)
936
936
937 # If we have an email, use it, otherwise
937 # If we have an email, use it, otherwise
938 # see if it contains a username we can get an email from
938 # see if it contains a username we can get an email from
939 if _email != '':
939 if _email != '':
940 return _email
940 return _email
941 else:
941 else:
942 user = User.get_by_username(
942 user = User.get_by_username(
943 author_name(author), case_insensitive=True, cache=True)
943 author_name(author), case_insensitive=True, cache=True)
944
944
945 if user is not None:
945 if user is not None:
946 return user.email
946 return user.email
947
947
948 # No valid email, not a valid user in the system, none!
948 # No valid email, not a valid user in the system, none!
949 return None
949 return None
950
950
951
951
952 def link_to_user(author, length=0, **kwargs):
952 def link_to_user(author, length=0, **kwargs):
953 user = discover_user(author)
953 user = discover_user(author)
954 # user can be None, but if we have it already it means we can re-use it
954 # user can be None, but if we have it already it means we can re-use it
955 # in the person() function, so we save 1 intensive-query
955 # in the person() function, so we save 1 intensive-query
956 if user:
956 if user:
957 author = user
957 author = user
958
958
959 display_person = person(author, 'username_or_name_or_email')
959 display_person = person(author, 'username_or_name_or_email')
960 if length:
960 if length:
961 display_person = shorter(display_person, length)
961 display_person = shorter(display_person, length)
962
962
963 if user and user.username != user.DEFAULT_USER:
963 if user and user.username != user.DEFAULT_USER:
964 return link_to(
964 return link_to(
965 escape(display_person),
965 escape(display_person),
966 route_path('user_profile', username=user.username),
966 route_path('user_profile', username=user.username),
967 **kwargs)
967 **kwargs)
968 else:
968 else:
969 return escape(display_person)
969 return escape(display_person)
970
970
971
971
972 def link_to_group(users_group_name, **kwargs):
972 def link_to_group(users_group_name, **kwargs):
973 return link_to(
973 return link_to(
974 escape(users_group_name),
974 escape(users_group_name),
975 route_path('user_group_profile', user_group_name=users_group_name),
975 route_path('user_group_profile', user_group_name=users_group_name),
976 **kwargs)
976 **kwargs)
977
977
978
978
979 def person(author, show_attr="username_and_name"):
979 def person(author, show_attr="username_and_name"):
980 user = discover_user(author)
980 user = discover_user(author)
981 if user:
981 if user:
982 return getattr(user, show_attr)
982 return getattr(user, show_attr)
983 else:
983 else:
984 _author = author_name(author)
984 _author = author_name(author)
985 _email = email(author)
985 _email = email(author)
986 return _author or _email
986 return _author or _email
987
987
988
988
989 def author_string(email):
989 def author_string(email):
990 if email:
990 if email:
991 user = User.get_by_email(email, case_insensitive=True, cache=True)
991 user = User.get_by_email(email, case_insensitive=True, cache=True)
992 if user:
992 if user:
993 if user.first_name or user.last_name:
993 if user.first_name or user.last_name:
994 return '%s %s &lt;%s&gt;' % (
994 return '%s %s &lt;%s&gt;' % (
995 user.first_name, user.last_name, email)
995 user.first_name, user.last_name, email)
996 else:
996 else:
997 return email
997 return email
998 else:
998 else:
999 return email
999 return email
1000 else:
1000 else:
1001 return None
1001 return None
1002
1002
1003
1003
1004 def person_by_id(id_, show_attr="username_and_name"):
1004 def person_by_id(id_, show_attr="username_and_name"):
1005 # attr to return from fetched user
1005 # attr to return from fetched user
1006 person_getter = lambda usr: getattr(usr, show_attr)
1006 person_getter = lambda usr: getattr(usr, show_attr)
1007
1007
1008 #maybe it's an ID ?
1008 #maybe it's an ID ?
1009 if str(id_).isdigit() or isinstance(id_, int):
1009 if str(id_).isdigit() or isinstance(id_, int):
1010 id_ = int(id_)
1010 id_ = int(id_)
1011 user = User.get(id_)
1011 user = User.get(id_)
1012 if user is not None:
1012 if user is not None:
1013 return person_getter(user)
1013 return person_getter(user)
1014 return id_
1014 return id_
1015
1015
1016
1016
1017 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
1017 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
1018 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
1018 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
1019 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
1019 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
1020
1020
1021
1021
1022 tags_paterns = OrderedDict((
1022 tags_paterns = OrderedDict((
1023 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
1023 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
1024 '<div class="metatag" tag="lang">\\2</div>')),
1024 '<div class="metatag" tag="lang">\\2</div>')),
1025
1025
1026 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1026 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1027 '<div class="metatag" tag="see">see: \\1 </div>')),
1027 '<div class="metatag" tag="see">see: \\1 </div>')),
1028
1028
1029 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
1029 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
1030 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
1030 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
1031
1031
1032 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1032 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1033 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
1033 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
1034
1034
1035 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
1035 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
1036 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
1036 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
1037
1037
1038 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
1038 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
1039 '<div class="metatag" tag="state \\1">\\1</div>')),
1039 '<div class="metatag" tag="state \\1">\\1</div>')),
1040
1040
1041 # label in grey
1041 # label in grey
1042 ('label', (re.compile(r'\[([a-z]+)\]'),
1042 ('label', (re.compile(r'\[([a-z]+)\]'),
1043 '<div class="metatag" tag="label">\\1</div>')),
1043 '<div class="metatag" tag="label">\\1</div>')),
1044
1044
1045 # generic catch all in grey
1045 # generic catch all in grey
1046 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
1046 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
1047 '<div class="metatag" tag="generic">\\1</div>')),
1047 '<div class="metatag" tag="generic">\\1</div>')),
1048 ))
1048 ))
1049
1049
1050
1050
1051 def extract_metatags(value):
1051 def extract_metatags(value):
1052 """
1052 """
1053 Extract supported meta-tags from given text value
1053 Extract supported meta-tags from given text value
1054 """
1054 """
1055 tags = []
1055 tags = []
1056 if not value:
1056 if not value:
1057 return tags, ''
1057 return tags, ''
1058
1058
1059 for key, val in tags_paterns.items():
1059 for key, val in tags_paterns.items():
1060 pat, replace_html = val
1060 pat, replace_html = val
1061 tags.extend([(key, x.group()) for x in pat.finditer(value)])
1061 tags.extend([(key, x.group()) for x in pat.finditer(value)])
1062 value = pat.sub('', value)
1062 value = pat.sub('', value)
1063
1063
1064 return tags, value
1064 return tags, value
1065
1065
1066
1066
1067 def style_metatag(tag_type, value):
1067 def style_metatag(tag_type, value):
1068 """
1068 """
1069 converts tags from value into html equivalent
1069 converts tags from value into html equivalent
1070 """
1070 """
1071 if not value:
1071 if not value:
1072 return ''
1072 return ''
1073
1073
1074 html_value = value
1074 html_value = value
1075 tag_data = tags_paterns.get(tag_type)
1075 tag_data = tags_paterns.get(tag_type)
1076 if tag_data:
1076 if tag_data:
1077 pat, replace_html = tag_data
1077 pat, replace_html = tag_data
1078 # convert to plain `unicode` instead of a markup tag to be used in
1078 # convert to plain `unicode` instead of a markup tag to be used in
1079 # regex expressions. safe_unicode doesn't work here
1079 # regex expressions. safe_unicode doesn't work here
1080 html_value = pat.sub(replace_html, unicode(value))
1080 html_value = pat.sub(replace_html, unicode(value))
1081
1081
1082 return html_value
1082 return html_value
1083
1083
1084
1084
1085 def bool2icon(value, show_at_false=True):
1085 def bool2icon(value, show_at_false=True):
1086 """
1086 """
1087 Returns boolean value of a given value, represented as html element with
1087 Returns boolean value of a given value, represented as html element with
1088 classes that will represent icons
1088 classes that will represent icons
1089
1089
1090 :param value: given value to convert to html node
1090 :param value: given value to convert to html node
1091 """
1091 """
1092
1092
1093 if value: # does bool conversion
1093 if value: # does bool conversion
1094 return HTML.tag('i', class_="icon-true", title='True')
1094 return HTML.tag('i', class_="icon-true", title='True')
1095 else: # not true as bool
1095 else: # not true as bool
1096 if show_at_false:
1096 if show_at_false:
1097 return HTML.tag('i', class_="icon-false", title='False')
1097 return HTML.tag('i', class_="icon-false", title='False')
1098 return HTML.tag('i')
1098 return HTML.tag('i')
1099
1099
1100 #==============================================================================
1100 #==============================================================================
1101 # PERMS
1101 # PERMS
1102 #==============================================================================
1102 #==============================================================================
1103 from rhodecode.lib.auth import (
1103 from rhodecode.lib.auth import (
1104 HasPermissionAny, HasPermissionAll,
1104 HasPermissionAny, HasPermissionAll,
1105 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll,
1105 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll,
1106 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token,
1106 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token,
1107 csrf_token_key, AuthUser)
1107 csrf_token_key, AuthUser)
1108
1108
1109
1109
1110 #==============================================================================
1110 #==============================================================================
1111 # GRAVATAR URL
1111 # GRAVATAR URL
1112 #==============================================================================
1112 #==============================================================================
1113 class InitialsGravatar(object):
1113 class InitialsGravatar(object):
1114 def __init__(self, email_address, first_name, last_name, size=30,
1114 def __init__(self, email_address, first_name, last_name, size=30,
1115 background=None, text_color='#fff'):
1115 background=None, text_color='#fff'):
1116 self.size = size
1116 self.size = size
1117 self.first_name = first_name
1117 self.first_name = first_name
1118 self.last_name = last_name
1118 self.last_name = last_name
1119 self.email_address = email_address
1119 self.email_address = email_address
1120 self.background = background or self.str2color(email_address)
1120 self.background = background or self.str2color(email_address)
1121 self.text_color = text_color
1121 self.text_color = text_color
1122
1122
1123 def get_color_bank(self):
1123 def get_color_bank(self):
1124 """
1124 """
1125 returns a predefined list of colors that gravatars can use.
1125 returns a predefined list of colors that gravatars can use.
1126 Those are randomized distinct colors that guarantee readability and
1126 Those are randomized distinct colors that guarantee readability and
1127 uniqueness.
1127 uniqueness.
1128
1128
1129 generated with: http://phrogz.net/css/distinct-colors.html
1129 generated with: http://phrogz.net/css/distinct-colors.html
1130 """
1130 """
1131 return [
1131 return [
1132 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1132 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1133 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1133 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1134 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1134 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1135 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1135 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1136 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1136 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1137 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1137 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1138 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1138 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1139 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1139 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1140 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1140 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1141 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1141 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1142 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1142 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1143 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1143 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1144 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1144 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1145 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1145 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1146 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1146 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1147 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1147 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1148 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1148 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1149 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1149 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1150 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1150 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1151 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1151 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1152 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1152 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1153 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1153 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1154 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1154 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1155 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1155 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1156 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1156 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1157 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1157 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1158 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1158 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1159 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1159 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1160 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1160 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1161 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1161 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1162 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1162 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1163 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1163 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1164 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1164 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1165 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1165 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1166 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1166 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1167 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1167 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1168 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1168 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1169 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1169 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1170 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1170 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1171 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1171 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1172 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1172 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1173 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1173 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1174 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1174 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1175 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1175 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1176 '#4f8c46', '#368dd9', '#5c0073'
1176 '#4f8c46', '#368dd9', '#5c0073'
1177 ]
1177 ]
1178
1178
1179 def rgb_to_hex_color(self, rgb_tuple):
1179 def rgb_to_hex_color(self, rgb_tuple):
1180 """
1180 """
1181 Converts an rgb_tuple passed to an hex color.
1181 Converts an rgb_tuple passed to an hex color.
1182
1182
1183 :param rgb_tuple: tuple with 3 ints represents rgb color space
1183 :param rgb_tuple: tuple with 3 ints represents rgb color space
1184 """
1184 """
1185 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1185 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1186
1186
1187 def email_to_int_list(self, email_str):
1187 def email_to_int_list(self, email_str):
1188 """
1188 """
1189 Get every byte of the hex digest value of email and turn it to integer.
1189 Get every byte of the hex digest value of email and turn it to integer.
1190 It's going to be always between 0-255
1190 It's going to be always between 0-255
1191 """
1191 """
1192 digest = md5_safe(email_str.lower())
1192 digest = md5_safe(email_str.lower())
1193 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1193 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1194
1194
1195 def pick_color_bank_index(self, email_str, color_bank):
1195 def pick_color_bank_index(self, email_str, color_bank):
1196 return self.email_to_int_list(email_str)[0] % len(color_bank)
1196 return self.email_to_int_list(email_str)[0] % len(color_bank)
1197
1197
1198 def str2color(self, email_str):
1198 def str2color(self, email_str):
1199 """
1199 """
1200 Tries to map in a stable algorithm an email to color
1200 Tries to map in a stable algorithm an email to color
1201
1201
1202 :param email_str:
1202 :param email_str:
1203 """
1203 """
1204 color_bank = self.get_color_bank()
1204 color_bank = self.get_color_bank()
1205 # pick position (module it's length so we always find it in the
1205 # pick position (module it's length so we always find it in the
1206 # bank even if it's smaller than 256 values
1206 # bank even if it's smaller than 256 values
1207 pos = self.pick_color_bank_index(email_str, color_bank)
1207 pos = self.pick_color_bank_index(email_str, color_bank)
1208 return color_bank[pos]
1208 return color_bank[pos]
1209
1209
1210 def normalize_email(self, email_address):
1210 def normalize_email(self, email_address):
1211 import unicodedata
1211 import unicodedata
1212 # default host used to fill in the fake/missing email
1212 # default host used to fill in the fake/missing email
1213 default_host = u'localhost'
1213 default_host = u'localhost'
1214
1214
1215 if not email_address:
1215 if not email_address:
1216 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1216 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1217
1217
1218 email_address = safe_unicode(email_address)
1218 email_address = safe_unicode(email_address)
1219
1219
1220 if u'@' not in email_address:
1220 if u'@' not in email_address:
1221 email_address = u'%s@%s' % (email_address, default_host)
1221 email_address = u'%s@%s' % (email_address, default_host)
1222
1222
1223 if email_address.endswith(u'@'):
1223 if email_address.endswith(u'@'):
1224 email_address = u'%s%s' % (email_address, default_host)
1224 email_address = u'%s%s' % (email_address, default_host)
1225
1225
1226 email_address = unicodedata.normalize('NFKD', email_address)\
1226 email_address = unicodedata.normalize('NFKD', email_address)\
1227 .encode('ascii', 'ignore')
1227 .encode('ascii', 'ignore')
1228 return email_address
1228 return email_address
1229
1229
1230 def get_initials(self):
1230 def get_initials(self):
1231 """
1231 """
1232 Returns 2 letter initials calculated based on the input.
1232 Returns 2 letter initials calculated based on the input.
1233 The algorithm picks first given email address, and takes first letter
1233 The algorithm picks first given email address, and takes first letter
1234 of part before @, and then the first letter of server name. In case
1234 of part before @, and then the first letter of server name. In case
1235 the part before @ is in a format of `somestring.somestring2` it replaces
1235 the part before @ is in a format of `somestring.somestring2` it replaces
1236 the server letter with first letter of somestring2
1236 the server letter with first letter of somestring2
1237
1237
1238 In case function was initialized with both first and lastname, this
1238 In case function was initialized with both first and lastname, this
1239 overrides the extraction from email by first letter of the first and
1239 overrides the extraction from email by first letter of the first and
1240 last name. We add special logic to that functionality, In case Full name
1240 last name. We add special logic to that functionality, In case Full name
1241 is compound, like Guido Von Rossum, we use last part of the last name
1241 is compound, like Guido Von Rossum, we use last part of the last name
1242 (Von Rossum) picking `R`.
1242 (Von Rossum) picking `R`.
1243
1243
1244 Function also normalizes the non-ascii characters to they ascii
1244 Function also normalizes the non-ascii characters to they ascii
1245 representation, eg Δ„ => A
1245 representation, eg Δ„ => A
1246 """
1246 """
1247 import unicodedata
1247 import unicodedata
1248 # replace non-ascii to ascii
1248 # replace non-ascii to ascii
1249 first_name = unicodedata.normalize(
1249 first_name = unicodedata.normalize(
1250 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1250 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1251 last_name = unicodedata.normalize(
1251 last_name = unicodedata.normalize(
1252 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1252 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1253
1253
1254 # do NFKD encoding, and also make sure email has proper format
1254 # do NFKD encoding, and also make sure email has proper format
1255 email_address = self.normalize_email(self.email_address)
1255 email_address = self.normalize_email(self.email_address)
1256
1256
1257 # first push the email initials
1257 # first push the email initials
1258 prefix, server = email_address.split('@', 1)
1258 prefix, server = email_address.split('@', 1)
1259
1259
1260 # check if prefix is maybe a 'first_name.last_name' syntax
1260 # check if prefix is maybe a 'first_name.last_name' syntax
1261 _dot_split = prefix.rsplit('.', 1)
1261 _dot_split = prefix.rsplit('.', 1)
1262 if len(_dot_split) == 2 and _dot_split[1]:
1262 if len(_dot_split) == 2 and _dot_split[1]:
1263 initials = [_dot_split[0][0], _dot_split[1][0]]
1263 initials = [_dot_split[0][0], _dot_split[1][0]]
1264 else:
1264 else:
1265 initials = [prefix[0], server[0]]
1265 initials = [prefix[0], server[0]]
1266
1266
1267 # then try to replace either first_name or last_name
1267 # then try to replace either first_name or last_name
1268 fn_letter = (first_name or " ")[0].strip()
1268 fn_letter = (first_name or " ")[0].strip()
1269 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1269 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1270
1270
1271 if fn_letter:
1271 if fn_letter:
1272 initials[0] = fn_letter
1272 initials[0] = fn_letter
1273
1273
1274 if ln_letter:
1274 if ln_letter:
1275 initials[1] = ln_letter
1275 initials[1] = ln_letter
1276
1276
1277 return ''.join(initials).upper()
1277 return ''.join(initials).upper()
1278
1278
1279 def get_img_data_by_type(self, font_family, img_type):
1279 def get_img_data_by_type(self, font_family, img_type):
1280 default_user = """
1280 default_user = """
1281 <svg xmlns="http://www.w3.org/2000/svg"
1281 <svg xmlns="http://www.w3.org/2000/svg"
1282 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1282 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1283 viewBox="-15 -10 439.165 429.164"
1283 viewBox="-15 -10 439.165 429.164"
1284
1284
1285 xml:space="preserve"
1285 xml:space="preserve"
1286 style="background:{background};" >
1286 style="background:{background};" >
1287
1287
1288 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1288 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1289 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1289 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1290 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1290 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1291 168.596,153.916,216.671,
1291 168.596,153.916,216.671,
1292 204.583,216.671z" fill="{text_color}"/>
1292 204.583,216.671z" fill="{text_color}"/>
1293 <path d="M407.164,374.717L360.88,
1293 <path d="M407.164,374.717L360.88,
1294 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1294 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1295 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1295 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1296 15.366-44.203,23.488-69.076,23.488c-24.877,
1296 15.366-44.203,23.488-69.076,23.488c-24.877,
1297 0-48.762-8.122-69.078-23.488
1297 0-48.762-8.122-69.078-23.488
1298 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1298 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1299 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1299 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1300 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1300 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1301 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1301 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1302 19.402-10.527 C409.699,390.129,
1302 19.402-10.527 C409.699,390.129,
1303 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1303 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1304 </svg>""".format(
1304 </svg>""".format(
1305 size=self.size,
1305 size=self.size,
1306 background='#979797', # @grey4
1306 background='#979797', # @grey4
1307 text_color=self.text_color,
1307 text_color=self.text_color,
1308 font_family=font_family)
1308 font_family=font_family)
1309
1309
1310 return {
1310 return {
1311 "default_user": default_user
1311 "default_user": default_user
1312 }[img_type]
1312 }[img_type]
1313
1313
1314 def get_img_data(self, svg_type=None):
1314 def get_img_data(self, svg_type=None):
1315 """
1315 """
1316 generates the svg metadata for image
1316 generates the svg metadata for image
1317 """
1317 """
1318 fonts = [
1318 fonts = [
1319 '-apple-system',
1319 '-apple-system',
1320 'BlinkMacSystemFont',
1320 'BlinkMacSystemFont',
1321 'Segoe UI',
1321 'Segoe UI',
1322 'Roboto',
1322 'Roboto',
1323 'Oxygen-Sans',
1323 'Oxygen-Sans',
1324 'Ubuntu',
1324 'Ubuntu',
1325 'Cantarell',
1325 'Cantarell',
1326 'Helvetica Neue',
1326 'Helvetica Neue',
1327 'sans-serif'
1327 'sans-serif'
1328 ]
1328 ]
1329 font_family = ','.join(fonts)
1329 font_family = ','.join(fonts)
1330 if svg_type:
1330 if svg_type:
1331 return self.get_img_data_by_type(font_family, svg_type)
1331 return self.get_img_data_by_type(font_family, svg_type)
1332
1332
1333 initials = self.get_initials()
1333 initials = self.get_initials()
1334 img_data = """
1334 img_data = """
1335 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1335 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1336 width="{size}" height="{size}"
1336 width="{size}" height="{size}"
1337 style="width: 100%; height: 100%; background-color: {background}"
1337 style="width: 100%; height: 100%; background-color: {background}"
1338 viewBox="0 0 {size} {size}">
1338 viewBox="0 0 {size} {size}">
1339 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1339 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1340 pointer-events="auto" fill="{text_color}"
1340 pointer-events="auto" fill="{text_color}"
1341 font-family="{font_family}"
1341 font-family="{font_family}"
1342 style="font-weight: 400; font-size: {f_size}px;">{text}
1342 style="font-weight: 400; font-size: {f_size}px;">{text}
1343 </text>
1343 </text>
1344 </svg>""".format(
1344 </svg>""".format(
1345 size=self.size,
1345 size=self.size,
1346 f_size=self.size/2.05, # scale the text inside the box nicely
1346 f_size=self.size/2.05, # scale the text inside the box nicely
1347 background=self.background,
1347 background=self.background,
1348 text_color=self.text_color,
1348 text_color=self.text_color,
1349 text=initials.upper(),
1349 text=initials.upper(),
1350 font_family=font_family)
1350 font_family=font_family)
1351
1351
1352 return img_data
1352 return img_data
1353
1353
1354 def generate_svg(self, svg_type=None):
1354 def generate_svg(self, svg_type=None):
1355 img_data = self.get_img_data(svg_type)
1355 img_data = self.get_img_data(svg_type)
1356 return "data:image/svg+xml;base64,%s" % base64.b64encode(img_data)
1356 return "data:image/svg+xml;base64,%s" % base64.b64encode(img_data)
1357
1357
1358
1358
1359 def initials_gravatar(email_address, first_name, last_name, size=30):
1359 def initials_gravatar(email_address, first_name, last_name, size=30):
1360 svg_type = None
1360 svg_type = None
1361 if email_address == User.DEFAULT_USER_EMAIL:
1361 if email_address == User.DEFAULT_USER_EMAIL:
1362 svg_type = 'default_user'
1362 svg_type = 'default_user'
1363 klass = InitialsGravatar(email_address, first_name, last_name, size)
1363 klass = InitialsGravatar(email_address, first_name, last_name, size)
1364 return klass.generate_svg(svg_type=svg_type)
1364 return klass.generate_svg(svg_type=svg_type)
1365
1365
1366
1366
1367 def gravatar_url(email_address, size=30, request=None):
1367 def gravatar_url(email_address, size=30, request=None):
1368 request = get_current_request()
1368 request = get_current_request()
1369 _use_gravatar = request.call_context.visual.use_gravatar
1369 _use_gravatar = request.call_context.visual.use_gravatar
1370 _gravatar_url = request.call_context.visual.gravatar_url
1370 _gravatar_url = request.call_context.visual.gravatar_url
1371
1371
1372 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1372 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1373
1373
1374 email_address = email_address or User.DEFAULT_USER_EMAIL
1374 email_address = email_address or User.DEFAULT_USER_EMAIL
1375 if isinstance(email_address, unicode):
1375 if isinstance(email_address, unicode):
1376 # hashlib crashes on unicode items
1376 # hashlib crashes on unicode items
1377 email_address = safe_str(email_address)
1377 email_address = safe_str(email_address)
1378
1378
1379 # empty email or default user
1379 # empty email or default user
1380 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1380 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1381 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1381 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1382
1382
1383 if _use_gravatar:
1383 if _use_gravatar:
1384 # TODO: Disuse pyramid thread locals. Think about another solution to
1384 # TODO: Disuse pyramid thread locals. Think about another solution to
1385 # get the host and schema here.
1385 # get the host and schema here.
1386 request = get_current_request()
1386 request = get_current_request()
1387 tmpl = safe_str(_gravatar_url)
1387 tmpl = safe_str(_gravatar_url)
1388 tmpl = tmpl.replace('{email}', email_address)\
1388 tmpl = tmpl.replace('{email}', email_address)\
1389 .replace('{md5email}', md5_safe(email_address.lower())) \
1389 .replace('{md5email}', md5_safe(email_address.lower())) \
1390 .replace('{netloc}', request.host)\
1390 .replace('{netloc}', request.host)\
1391 .replace('{scheme}', request.scheme)\
1391 .replace('{scheme}', request.scheme)\
1392 .replace('{size}', safe_str(size))
1392 .replace('{size}', safe_str(size))
1393 return tmpl
1393 return tmpl
1394 else:
1394 else:
1395 return initials_gravatar(email_address, '', '', size=size)
1395 return initials_gravatar(email_address, '', '', size=size)
1396
1396
1397
1397
1398 def breadcrumb_repo_link(repo):
1398 def breadcrumb_repo_link(repo):
1399 """
1399 """
1400 Makes a breadcrumbs path link to repo
1400 Makes a breadcrumbs path link to repo
1401
1401
1402 ex::
1402 ex::
1403 group >> subgroup >> repo
1403 group >> subgroup >> repo
1404
1404
1405 :param repo: a Repository instance
1405 :param repo: a Repository instance
1406 """
1406 """
1407
1407
1408 path = [
1408 path = [
1409 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1409 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1410 title='last change:{}'.format(format_date(group.last_commit_change)))
1410 title='last change:{}'.format(format_date(group.last_commit_change)))
1411 for group in repo.groups_with_parents
1411 for group in repo.groups_with_parents
1412 ] + [
1412 ] + [
1413 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1413 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1414 title='last change:{}'.format(format_date(repo.last_commit_change)))
1414 title='last change:{}'.format(format_date(repo.last_commit_change)))
1415 ]
1415 ]
1416
1416
1417 return literal(' &raquo; '.join(path))
1417 return literal(' &raquo; '.join(path))
1418
1418
1419
1419
1420 def breadcrumb_repo_group_link(repo_group):
1420 def breadcrumb_repo_group_link(repo_group):
1421 """
1421 """
1422 Makes a breadcrumbs path link to repo
1422 Makes a breadcrumbs path link to repo
1423
1423
1424 ex::
1424 ex::
1425 group >> subgroup
1425 group >> subgroup
1426
1426
1427 :param repo_group: a Repository Group instance
1427 :param repo_group: a Repository Group instance
1428 """
1428 """
1429
1429
1430 path = [
1430 path = [
1431 link_to(group.name,
1431 link_to(group.name,
1432 route_path('repo_group_home', repo_group_name=group.group_name),
1432 route_path('repo_group_home', repo_group_name=group.group_name),
1433 title='last change:{}'.format(format_date(group.last_commit_change)))
1433 title='last change:{}'.format(format_date(group.last_commit_change)))
1434 for group in repo_group.parents
1434 for group in repo_group.parents
1435 ] + [
1435 ] + [
1436 link_to(repo_group.name,
1436 link_to(repo_group.name,
1437 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1437 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1438 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1438 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1439 ]
1439 ]
1440
1440
1441 return literal(' &raquo; '.join(path))
1441 return literal(' &raquo; '.join(path))
1442
1442
1443
1443
1444 def format_byte_size_binary(file_size):
1444 def format_byte_size_binary(file_size):
1445 """
1445 """
1446 Formats file/folder sizes to standard.
1446 Formats file/folder sizes to standard.
1447 """
1447 """
1448 if file_size is None:
1448 if file_size is None:
1449 file_size = 0
1449 file_size = 0
1450
1450
1451 formatted_size = format_byte_size(file_size, binary=True)
1451 formatted_size = format_byte_size(file_size, binary=True)
1452 return formatted_size
1452 return formatted_size
1453
1453
1454
1454
1455 def urlify_text(text_, safe=True, **href_attrs):
1455 def urlify_text(text_, safe=True, **href_attrs):
1456 """
1456 """
1457 Extract urls from text and make html links out of them
1457 Extract urls from text and make html links out of them
1458 """
1458 """
1459
1459
1460 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1460 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1461 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1461 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1462
1462
1463 def url_func(match_obj):
1463 def url_func(match_obj):
1464 url_full = match_obj.groups()[0]
1464 url_full = match_obj.groups()[0]
1465 a_options = dict(href_attrs)
1465 a_options = dict(href_attrs)
1466 a_options['href'] = url_full
1466 a_options['href'] = url_full
1467 a_text = url_full
1467 a_text = url_full
1468 return HTML.tag("a", a_text, **a_options)
1468 return HTML.tag("a", a_text, **a_options)
1469
1469
1470 _new_text = url_pat.sub(url_func, text_)
1470 _new_text = url_pat.sub(url_func, text_)
1471
1471
1472 if safe:
1472 if safe:
1473 return literal(_new_text)
1473 return literal(_new_text)
1474 return _new_text
1474 return _new_text
1475
1475
1476
1476
1477 def urlify_commits(text_, repo_name):
1477 def urlify_commits(text_, repo_name):
1478 """
1478 """
1479 Extract commit ids from text and make link from them
1479 Extract commit ids from text and make link from them
1480
1480
1481 :param text_:
1481 :param text_:
1482 :param repo_name: repo name to build the URL with
1482 :param repo_name: repo name to build the URL with
1483 """
1483 """
1484
1484
1485 url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1485 url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1486
1486
1487 def url_func(match_obj):
1487 def url_func(match_obj):
1488 commit_id = match_obj.groups()[1]
1488 commit_id = match_obj.groups()[1]
1489 pref = match_obj.groups()[0]
1489 pref = match_obj.groups()[0]
1490 suf = match_obj.groups()[2]
1490 suf = match_obj.groups()[2]
1491
1491
1492 tmpl = (
1492 tmpl = (
1493 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-alt="%(hovercard_alt)s" data-hovercard-url="%(hovercard_url)s">'
1493 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-alt="%(hovercard_alt)s" data-hovercard-url="%(hovercard_url)s">'
1494 '%(commit_id)s</a>%(suf)s'
1494 '%(commit_id)s</a>%(suf)s'
1495 )
1495 )
1496 return tmpl % {
1496 return tmpl % {
1497 'pref': pref,
1497 'pref': pref,
1498 'cls': 'revision-link',
1498 'cls': 'revision-link',
1499 'url': route_url(
1499 'url': route_url(
1500 'repo_commit', repo_name=repo_name, commit_id=commit_id),
1500 'repo_commit', repo_name=repo_name, commit_id=commit_id),
1501 'commit_id': commit_id,
1501 'commit_id': commit_id,
1502 'suf': suf,
1502 'suf': suf,
1503 'hovercard_alt': 'Commit: {}'.format(commit_id),
1503 'hovercard_alt': 'Commit: {}'.format(commit_id),
1504 'hovercard_url': route_url(
1504 'hovercard_url': route_url(
1505 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)
1505 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)
1506 }
1506 }
1507
1507
1508 new_text = url_pat.sub(url_func, text_)
1508 new_text = url_pat.sub(url_func, text_)
1509
1509
1510 return new_text
1510 return new_text
1511
1511
1512
1512
1513 def _process_url_func(match_obj, repo_name, uid, entry,
1513 def _process_url_func(match_obj, repo_name, uid, entry,
1514 return_raw_data=False, link_format='html'):
1514 return_raw_data=False, link_format='html'):
1515 pref = ''
1515 pref = ''
1516 if match_obj.group().startswith(' '):
1516 if match_obj.group().startswith(' '):
1517 pref = ' '
1517 pref = ' '
1518
1518
1519 issue_id = ''.join(match_obj.groups())
1519 issue_id = ''.join(match_obj.groups())
1520
1520
1521 if link_format == 'html':
1521 if link_format == 'html':
1522 tmpl = (
1522 tmpl = (
1523 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1523 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1524 '%(issue-prefix)s%(id-repr)s'
1524 '%(issue-prefix)s%(id-repr)s'
1525 '</a>')
1525 '</a>')
1526 elif link_format == 'html+hovercard':
1526 elif link_format == 'html+hovercard':
1527 tmpl = (
1527 tmpl = (
1528 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1528 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1529 '%(issue-prefix)s%(id-repr)s'
1529 '%(issue-prefix)s%(id-repr)s'
1530 '</a>')
1530 '</a>')
1531 elif link_format in ['rst', 'rst+hovercard']:
1531 elif link_format in ['rst', 'rst+hovercard']:
1532 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1532 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1533 elif link_format in ['markdown', 'markdown+hovercard']:
1533 elif link_format in ['markdown', 'markdown+hovercard']:
1534 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1534 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1535 else:
1535 else:
1536 raise ValueError('Bad link_format:{}'.format(link_format))
1536 raise ValueError('Bad link_format:{}'.format(link_format))
1537
1537
1538 (repo_name_cleaned,
1538 (repo_name_cleaned,
1539 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1539 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1540
1540
1541 # variables replacement
1541 # variables replacement
1542 named_vars = {
1542 named_vars = {
1543 'id': issue_id,
1543 'id': issue_id,
1544 'repo': repo_name,
1544 'repo': repo_name,
1545 'repo_name': repo_name_cleaned,
1545 'repo_name': repo_name_cleaned,
1546 'group_name': parent_group_name,
1546 'group_name': parent_group_name,
1547 # set dummy keys so we always have them
1547 # set dummy keys so we always have them
1548 'hostname': '',
1548 'hostname': '',
1549 'netloc': '',
1549 'netloc': '',
1550 'scheme': ''
1550 'scheme': ''
1551 }
1551 }
1552
1552
1553 request = get_current_request()
1553 request = get_current_request()
1554 if request:
1554 if request:
1555 # exposes, hostname, netloc, scheme
1555 # exposes, hostname, netloc, scheme
1556 host_data = get_host_info(request)
1556 host_data = get_host_info(request)
1557 named_vars.update(host_data)
1557 named_vars.update(host_data)
1558
1558
1559 # named regex variables
1559 # named regex variables
1560 named_vars.update(match_obj.groupdict())
1560 named_vars.update(match_obj.groupdict())
1561 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1561 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1562 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1562 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1563 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1563 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1564
1564
1565 def quote_cleaner(input_str):
1565 def quote_cleaner(input_str):
1566 """Remove quotes as it's HTML"""
1566 """Remove quotes as it's HTML"""
1567 return input_str.replace('"', '')
1567 return input_str.replace('"', '')
1568
1568
1569 data = {
1569 data = {
1570 'pref': pref,
1570 'pref': pref,
1571 'cls': quote_cleaner('issue-tracker-link'),
1571 'cls': quote_cleaner('issue-tracker-link'),
1572 'url': quote_cleaner(_url),
1572 'url': quote_cleaner(_url),
1573 'id-repr': issue_id,
1573 'id-repr': issue_id,
1574 'issue-prefix': entry['pref'],
1574 'issue-prefix': entry['pref'],
1575 'serv': entry['url'],
1575 'serv': entry['url'],
1576 'title': bleach.clean(desc, strip=True),
1576 'title': bleach.clean(desc, strip=True),
1577 'hovercard_url': hovercard_url
1577 'hovercard_url': hovercard_url
1578 }
1578 }
1579
1579
1580 if return_raw_data:
1580 if return_raw_data:
1581 return {
1581 return {
1582 'id': issue_id,
1582 'id': issue_id,
1583 'url': _url
1583 'url': _url
1584 }
1584 }
1585 return tmpl % data
1585 return tmpl % data
1586
1586
1587
1587
1588 def get_active_pattern_entries(repo_name):
1588 def get_active_pattern_entries(repo_name):
1589 repo = None
1589 repo = None
1590 if repo_name:
1590 if repo_name:
1591 # Retrieving repo_name to avoid invalid repo_name to explode on
1591 # Retrieving repo_name to avoid invalid repo_name to explode on
1592 # IssueTrackerSettingsModel but still passing invalid name further down
1592 # IssueTrackerSettingsModel but still passing invalid name further down
1593 repo = Repository.get_by_repo_name(repo_name, cache=True)
1593 repo = Repository.get_by_repo_name(repo_name, cache=True)
1594
1594
1595 settings_model = IssueTrackerSettingsModel(repo=repo)
1595 settings_model = IssueTrackerSettingsModel(repo=repo)
1596 active_entries = settings_model.get_settings(cache=True)
1596 active_entries = settings_model.get_settings(cache=True)
1597 return active_entries
1597 return active_entries
1598
1598
1599
1599
1600 pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
1600 pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
1601
1601
1602
1602
1603 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1603 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1604
1604
1605 allowed_formats = ['html', 'rst', 'markdown',
1605 allowed_formats = ['html', 'rst', 'markdown',
1606 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1606 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1607 if link_format not in allowed_formats:
1607 if link_format not in allowed_formats:
1608 raise ValueError('Link format can be only one of:{} got {}'.format(
1608 raise ValueError('Link format can be only one of:{} got {}'.format(
1609 allowed_formats, link_format))
1609 allowed_formats, link_format))
1610
1610
1611 if active_entries is None:
1611 if active_entries is None:
1612 log.debug('Fetch active patterns for repo: %s', repo_name)
1612 log.debug('Fetch active patterns for repo: %s', repo_name)
1613 active_entries = get_active_pattern_entries(repo_name)
1613 active_entries = get_active_pattern_entries(repo_name)
1614
1614
1615 issues_data = []
1615 issues_data = []
1616 new_text = text_string
1616 new_text = text_string
1617
1617
1618 log.debug('Got %s entries to process', len(active_entries))
1618 log.debug('Got %s entries to process', len(active_entries))
1619 for uid, entry in active_entries.items():
1619 for uid, entry in active_entries.items():
1620 log.debug('found issue tracker entry with uid %s', uid)
1620 log.debug('found issue tracker entry with uid %s', uid)
1621
1621
1622 if not (entry['pat'] and entry['url']):
1622 if not (entry['pat'] and entry['url']):
1623 log.debug('skipping due to missing data')
1623 log.debug('skipping due to missing data')
1624 continue
1624 continue
1625
1625
1626 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1626 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1627 uid, entry['pat'], entry['url'], entry['pref'])
1627 uid, entry['pat'], entry['url'], entry['pref'])
1628
1628
1629 if entry.get('pat_compiled'):
1629 if entry.get('pat_compiled'):
1630 pattern = entry['pat_compiled']
1630 pattern = entry['pat_compiled']
1631 else:
1631 else:
1632 try:
1632 try:
1633 pattern = re.compile(r'%s' % entry['pat'])
1633 pattern = re.compile(r'%s' % entry['pat'])
1634 except re.error:
1634 except re.error:
1635 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1635 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1636 continue
1636 continue
1637
1637
1638 data_func = partial(
1638 data_func = partial(
1639 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1639 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1640 return_raw_data=True)
1640 return_raw_data=True)
1641
1641
1642 for match_obj in pattern.finditer(text_string):
1642 for match_obj in pattern.finditer(text_string):
1643 issues_data.append(data_func(match_obj))
1643 issues_data.append(data_func(match_obj))
1644
1644
1645 url_func = partial(
1645 url_func = partial(
1646 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1646 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1647 link_format=link_format)
1647 link_format=link_format)
1648
1648
1649 new_text = pattern.sub(url_func, new_text)
1649 new_text = pattern.sub(url_func, new_text)
1650 log.debug('processed prefix:uid `%s`', uid)
1650 log.debug('processed prefix:uid `%s`', uid)
1651
1651
1652 # finally use global replace, eg !123 -> pr-link, those will not catch
1652 # finally use global replace, eg !123 -> pr-link, those will not catch
1653 # if already similar pattern exists
1653 # if already similar pattern exists
1654 server_url = '${scheme}://${netloc}'
1654 server_url = '${scheme}://${netloc}'
1655 pr_entry = {
1655 pr_entry = {
1656 'pref': '!',
1656 'pref': '!',
1657 'url': server_url + '/_admin/pull-requests/${id}',
1657 'url': server_url + '/_admin/pull-requests/${id}',
1658 'desc': 'Pull Request !${id}',
1658 'desc': 'Pull Request !${id}',
1659 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1659 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1660 }
1660 }
1661 pr_url_func = partial(
1661 pr_url_func = partial(
1662 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1662 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1663 link_format=link_format+'+hovercard')
1663 link_format=link_format+'+hovercard')
1664 new_text = pr_pattern_re.sub(pr_url_func, new_text)
1664 new_text = pr_pattern_re.sub(pr_url_func, new_text)
1665 log.debug('processed !pr pattern')
1665 log.debug('processed !pr pattern')
1666
1666
1667 return new_text, issues_data
1667 return new_text, issues_data
1668
1668
1669
1669
1670 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1670 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1671 """
1671 """
1672 Parses given text message and makes proper links.
1672 Parses given text message and makes proper links.
1673 issues are linked to given issue-server, and rest is a commit link
1673 issues are linked to given issue-server, and rest is a commit link
1674 """
1674 """
1675
1675
1676 def escaper(_text):
1676 def escaper(_text):
1677 return _text.replace('<', '&lt;').replace('>', '&gt;')
1677 return _text.replace('<', '&lt;').replace('>', '&gt;')
1678
1678
1679 new_text = escaper(commit_text)
1679 new_text = escaper(commit_text)
1680
1680
1681 # extract http/https links and make them real urls
1681 # extract http/https links and make them real urls
1682 new_text = urlify_text(new_text, safe=False)
1682 new_text = urlify_text(new_text, safe=False)
1683
1683
1684 # urlify commits - extract commit ids and make link out of them, if we have
1684 # urlify commits - extract commit ids and make link out of them, if we have
1685 # the scope of repository present.
1685 # the scope of repository present.
1686 if repository:
1686 if repository:
1687 new_text = urlify_commits(new_text, repository)
1687 new_text = urlify_commits(new_text, repository)
1688
1688
1689 # process issue tracker patterns
1689 # process issue tracker patterns
1690 new_text, issues = process_patterns(new_text, repository or '',
1690 new_text, issues = process_patterns(new_text, repository or '',
1691 active_entries=active_pattern_entries)
1691 active_entries=active_pattern_entries)
1692
1692
1693 return literal(new_text)
1693 return literal(new_text)
1694
1694
1695
1695
1696 def render_binary(repo_name, file_obj):
1696 def render_binary(repo_name, file_obj):
1697 """
1697 """
1698 Choose how to render a binary file
1698 Choose how to render a binary file
1699 """
1699 """
1700
1700
1701 # unicode
1701 # unicode
1702 filename = file_obj.name
1702 filename = file_obj.name
1703
1703
1704 # images
1704 # images
1705 for ext in ['*.png', '*.jpeg', '*.jpg', '*.ico', '*.gif']:
1705 for ext in ['*.png', '*.jpeg', '*.jpg', '*.ico', '*.gif']:
1706 if fnmatch.fnmatch(filename, pat=ext):
1706 if fnmatch.fnmatch(filename, pat=ext):
1707 src = route_path(
1707 src = route_path(
1708 'repo_file_raw', repo_name=repo_name,
1708 'repo_file_raw', repo_name=repo_name,
1709 commit_id=file_obj.commit.raw_id,
1709 commit_id=file_obj.commit.raw_id,
1710 f_path=file_obj.path)
1710 f_path=file_obj.path)
1711
1711
1712 return literal(
1712 return literal(
1713 '<img class="rendered-binary" alt="rendered-image" src="{}">'.format(src))
1713 '<img class="rendered-binary" alt="rendered-image" src="{}">'.format(src))
1714
1714
1715
1715
1716 def renderer_from_filename(filename, exclude=None):
1716 def renderer_from_filename(filename, exclude=None):
1717 """
1717 """
1718 choose a renderer based on filename, this works only for text based files
1718 choose a renderer based on filename, this works only for text based files
1719 """
1719 """
1720
1720
1721 # ipython
1721 # ipython
1722 for ext in ['*.ipynb']:
1722 for ext in ['*.ipynb']:
1723 if fnmatch.fnmatch(filename, pat=ext):
1723 if fnmatch.fnmatch(filename, pat=ext):
1724 return 'jupyter'
1724 return 'jupyter'
1725
1725
1726 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1726 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1727 if is_markup:
1727 if is_markup:
1728 return is_markup
1728 return is_markup
1729 return None
1729 return None
1730
1730
1731
1731
1732 def render(source, renderer='rst', mentions=False, relative_urls=None,
1732 def render(source, renderer='rst', mentions=False, relative_urls=None,
1733 repo_name=None, active_pattern_entries=None):
1733 repo_name=None, active_pattern_entries=None):
1734
1734
1735 def maybe_convert_relative_links(html_source):
1735 def maybe_convert_relative_links(html_source):
1736 if relative_urls:
1736 if relative_urls:
1737 return relative_links(html_source, relative_urls)
1737 return relative_links(html_source, relative_urls)
1738 return html_source
1738 return html_source
1739
1739
1740 if renderer == 'plain':
1740 if renderer == 'plain':
1741 return literal(
1741 return literal(
1742 MarkupRenderer.plain(source, leading_newline=False))
1742 MarkupRenderer.plain(source, leading_newline=False))
1743
1743
1744 elif renderer == 'rst':
1744 elif renderer == 'rst':
1745 if repo_name:
1745 if repo_name:
1746 # process patterns on comments if we pass in repo name
1746 # process patterns on comments if we pass in repo name
1747 source, issues = process_patterns(
1747 source, issues = process_patterns(
1748 source, repo_name, link_format='rst',
1748 source, repo_name, link_format='rst',
1749 active_entries=active_pattern_entries)
1749 active_entries=active_pattern_entries)
1750
1750
1751 return literal(
1751 return literal(
1752 '<div class="rst-block">%s</div>' %
1752 '<div class="rst-block">%s</div>' %
1753 maybe_convert_relative_links(
1753 maybe_convert_relative_links(
1754 MarkupRenderer.rst(source, mentions=mentions)))
1754 MarkupRenderer.rst(source, mentions=mentions)))
1755
1755
1756 elif renderer == 'markdown':
1756 elif renderer == 'markdown':
1757 if repo_name:
1757 if repo_name:
1758 # process patterns on comments if we pass in repo name
1758 # process patterns on comments if we pass in repo name
1759 source, issues = process_patterns(
1759 source, issues = process_patterns(
1760 source, repo_name, link_format='markdown',
1760 source, repo_name, link_format='markdown',
1761 active_entries=active_pattern_entries)
1761 active_entries=active_pattern_entries)
1762
1762
1763 return literal(
1763 return literal(
1764 '<div class="markdown-block">%s</div>' %
1764 '<div class="markdown-block">%s</div>' %
1765 maybe_convert_relative_links(
1765 maybe_convert_relative_links(
1766 MarkupRenderer.markdown(source, flavored=True,
1766 MarkupRenderer.markdown(source, flavored=True,
1767 mentions=mentions)))
1767 mentions=mentions)))
1768
1768
1769 elif renderer == 'jupyter':
1769 elif renderer == 'jupyter':
1770 return literal(
1770 return literal(
1771 '<div class="ipynb">%s</div>' %
1771 '<div class="ipynb">%s</div>' %
1772 maybe_convert_relative_links(
1772 maybe_convert_relative_links(
1773 MarkupRenderer.jupyter(source)))
1773 MarkupRenderer.jupyter(source)))
1774
1774
1775 # None means just show the file-source
1775 # None means just show the file-source
1776 return None
1776 return None
1777
1777
1778
1778
1779 def commit_status(repo, commit_id):
1779 def commit_status(repo, commit_id):
1780 return ChangesetStatusModel().get_status(repo, commit_id)
1780 return ChangesetStatusModel().get_status(repo, commit_id)
1781
1781
1782
1782
1783 def commit_status_lbl(commit_status):
1783 def commit_status_lbl(commit_status):
1784 return dict(ChangesetStatus.STATUSES).get(commit_status)
1784 return dict(ChangesetStatus.STATUSES).get(commit_status)
1785
1785
1786
1786
1787 def commit_time(repo_name, commit_id):
1787 def commit_time(repo_name, commit_id):
1788 repo = Repository.get_by_repo_name(repo_name)
1788 repo = Repository.get_by_repo_name(repo_name)
1789 commit = repo.get_commit(commit_id=commit_id)
1789 commit = repo.get_commit(commit_id=commit_id)
1790 return commit.date
1790 return commit.date
1791
1791
1792
1792
1793 def get_permission_name(key):
1793 def get_permission_name(key):
1794 return dict(Permission.PERMS).get(key)
1794 return dict(Permission.PERMS).get(key)
1795
1795
1796
1796
1797 def journal_filter_help(request):
1797 def journal_filter_help(request):
1798 _ = request.translate
1798 _ = request.translate
1799 from rhodecode.lib.audit_logger import ACTIONS
1799 from rhodecode.lib.audit_logger import ACTIONS
1800 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1800 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1801
1801
1802 return _(
1802 return _(
1803 'Example filter terms:\n' +
1803 'Example filter terms:\n' +
1804 ' repository:vcs\n' +
1804 ' repository:vcs\n' +
1805 ' username:marcin\n' +
1805 ' username:marcin\n' +
1806 ' username:(NOT marcin)\n' +
1806 ' username:(NOT marcin)\n' +
1807 ' action:*push*\n' +
1807 ' action:*push*\n' +
1808 ' ip:127.0.0.1\n' +
1808 ' ip:127.0.0.1\n' +
1809 ' date:20120101\n' +
1809 ' date:20120101\n' +
1810 ' date:[20120101100000 TO 20120102]\n' +
1810 ' date:[20120101100000 TO 20120102]\n' +
1811 '\n' +
1811 '\n' +
1812 'Actions: {actions}\n' +
1812 'Actions: {actions}\n' +
1813 '\n' +
1813 '\n' +
1814 'Generate wildcards using \'*\' character:\n' +
1814 'Generate wildcards using \'*\' character:\n' +
1815 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1815 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1816 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1816 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1817 '\n' +
1817 '\n' +
1818 'Optional AND / OR operators in queries\n' +
1818 'Optional AND / OR operators in queries\n' +
1819 ' "repository:vcs OR repository:test"\n' +
1819 ' "repository:vcs OR repository:test"\n' +
1820 ' "username:test AND repository:test*"\n'
1820 ' "username:test AND repository:test*"\n'
1821 ).format(actions=actions)
1821 ).format(actions=actions)
1822
1822
1823
1823
1824 def not_mapped_error(repo_name):
1824 def not_mapped_error(repo_name):
1825 from rhodecode.translation import _
1825 from rhodecode.translation import _
1826 flash(_('%s repository is not mapped to db perhaps'
1826 flash(_('%s repository is not mapped to db perhaps'
1827 ' it was created or renamed from the filesystem'
1827 ' it was created or renamed from the filesystem'
1828 ' please run the application again'
1828 ' please run the application again'
1829 ' in order to rescan repositories') % repo_name, category='error')
1829 ' in order to rescan repositories') % repo_name, category='error')
1830
1830
1831
1831
1832 def ip_range(ip_addr):
1832 def ip_range(ip_addr):
1833 from rhodecode.model.db import UserIpMap
1833 from rhodecode.model.db import UserIpMap
1834 s, e = UserIpMap._get_ip_range(ip_addr)
1834 s, e = UserIpMap._get_ip_range(ip_addr)
1835 return '%s - %s' % (s, e)
1835 return '%s - %s' % (s, e)
1836
1836
1837
1837
1838 def form(url, method='post', needs_csrf_token=True, **attrs):
1838 def form(url, method='post', needs_csrf_token=True, **attrs):
1839 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1839 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1840 if method.lower() != 'get' and needs_csrf_token:
1840 if method.lower() != 'get' and needs_csrf_token:
1841 raise Exception(
1841 raise Exception(
1842 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1842 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1843 'CSRF token. If the endpoint does not require such token you can ' +
1843 'CSRF token. If the endpoint does not require such token you can ' +
1844 'explicitly set the parameter needs_csrf_token to false.')
1844 'explicitly set the parameter needs_csrf_token to false.')
1845
1845
1846 return insecure_form(url, method=method, **attrs)
1846 return insecure_form(url, method=method, **attrs)
1847
1847
1848
1848
1849 def secure_form(form_url, method="POST", multipart=False, **attrs):
1849 def secure_form(form_url, method="POST", multipart=False, **attrs):
1850 """Start a form tag that points the action to an url. This
1850 """Start a form tag that points the action to an url. This
1851 form tag will also include the hidden field containing
1851 form tag will also include the hidden field containing
1852 the auth token.
1852 the auth token.
1853
1853
1854 The url options should be given either as a string, or as a
1854 The url options should be given either as a string, or as a
1855 ``url()`` function. The method for the form defaults to POST.
1855 ``url()`` function. The method for the form defaults to POST.
1856
1856
1857 Options:
1857 Options:
1858
1858
1859 ``multipart``
1859 ``multipart``
1860 If set to True, the enctype is set to "multipart/form-data".
1860 If set to True, the enctype is set to "multipart/form-data".
1861 ``method``
1861 ``method``
1862 The method to use when submitting the form, usually either
1862 The method to use when submitting the form, usually either
1863 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1863 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1864 hidden input with name _method is added to simulate the verb
1864 hidden input with name _method is added to simulate the verb
1865 over POST.
1865 over POST.
1866
1866
1867 """
1867 """
1868
1868
1869 if 'request' in attrs:
1869 if 'request' in attrs:
1870 session = attrs['request'].session
1870 session = attrs['request'].session
1871 del attrs['request']
1871 del attrs['request']
1872 else:
1872 else:
1873 raise ValueError(
1873 raise ValueError(
1874 'Calling this form requires request= to be passed as argument')
1874 'Calling this form requires request= to be passed as argument')
1875
1875
1876 _form = insecure_form(form_url, method, multipart, **attrs)
1876 _form = insecure_form(form_url, method, multipart, **attrs)
1877 token = literal(
1877 token = literal(
1878 '<input type="hidden" name="{}" value="{}">'.format(
1878 '<input type="hidden" name="{}" value="{}">'.format(
1879 csrf_token_key, get_csrf_token(session)))
1879 csrf_token_key, get_csrf_token(session)))
1880
1880
1881 return literal("%s\n%s" % (_form, token))
1881 return literal("%s\n%s" % (_form, token))
1882
1882
1883
1883
1884 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1884 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1885 select_html = select(name, selected, options, **attrs)
1885 select_html = select(name, selected, options, **attrs)
1886
1886
1887 select2 = """
1887 select2 = """
1888 <script>
1888 <script>
1889 $(document).ready(function() {
1889 $(document).ready(function() {
1890 $('#%s').select2({
1890 $('#%s').select2({
1891 containerCssClass: 'drop-menu %s',
1891 containerCssClass: 'drop-menu %s',
1892 dropdownCssClass: 'drop-menu-dropdown',
1892 dropdownCssClass: 'drop-menu-dropdown',
1893 dropdownAutoWidth: true%s
1893 dropdownAutoWidth: true%s
1894 });
1894 });
1895 });
1895 });
1896 </script>
1896 </script>
1897 """
1897 """
1898
1898
1899 filter_option = """,
1899 filter_option = """,
1900 minimumResultsForSearch: -1
1900 minimumResultsForSearch: -1
1901 """
1901 """
1902 input_id = attrs.get('id') or name
1902 input_id = attrs.get('id') or name
1903 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1903 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1904 filter_enabled = "" if enable_filter else filter_option
1904 filter_enabled = "" if enable_filter else filter_option
1905 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1905 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1906
1906
1907 return literal(select_html+select_script)
1907 return literal(select_html+select_script)
1908
1908
1909
1909
1910 def get_visual_attr(tmpl_context_var, attr_name):
1910 def get_visual_attr(tmpl_context_var, attr_name):
1911 """
1911 """
1912 A safe way to get a variable from visual variable of template context
1912 A safe way to get a variable from visual variable of template context
1913
1913
1914 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1914 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1915 :param attr_name: name of the attribute we fetch from the c.visual
1915 :param attr_name: name of the attribute we fetch from the c.visual
1916 """
1916 """
1917 visual = getattr(tmpl_context_var, 'visual', None)
1917 visual = getattr(tmpl_context_var, 'visual', None)
1918 if not visual:
1918 if not visual:
1919 return
1919 return
1920 else:
1920 else:
1921 return getattr(visual, attr_name, None)
1921 return getattr(visual, attr_name, None)
1922
1922
1923
1923
1924 def get_last_path_part(file_node):
1924 def get_last_path_part(file_node):
1925 if not file_node.path:
1925 if not file_node.path:
1926 return u'/'
1926 return u'/'
1927
1927
1928 path = safe_unicode(file_node.path.split('/')[-1])
1928 path = safe_unicode(file_node.path.split('/')[-1])
1929 return u'../' + path
1929 return u'../' + path
1930
1930
1931
1931
1932 def route_url(*args, **kwargs):
1932 def route_url(*args, **kwargs):
1933 """
1933 """
1934 Wrapper around pyramids `route_url` (fully qualified url) function.
1934 Wrapper around pyramids `route_url` (fully qualified url) function.
1935 """
1935 """
1936 req = get_current_request()
1936 req = get_current_request()
1937 return req.route_url(*args, **kwargs)
1937 return req.route_url(*args, **kwargs)
1938
1938
1939
1939
1940 def route_path(*args, **kwargs):
1940 def route_path(*args, **kwargs):
1941 """
1941 """
1942 Wrapper around pyramids `route_path` function.
1942 Wrapper around pyramids `route_path` function.
1943 """
1943 """
1944 req = get_current_request()
1944 req = get_current_request()
1945 return req.route_path(*args, **kwargs)
1945 return req.route_path(*args, **kwargs)
1946
1946
1947
1947
1948 def route_path_or_none(*args, **kwargs):
1948 def route_path_or_none(*args, **kwargs):
1949 try:
1949 try:
1950 return route_path(*args, **kwargs)
1950 return route_path(*args, **kwargs)
1951 except KeyError:
1951 except KeyError:
1952 return None
1952 return None
1953
1953
1954
1954
1955 def current_route_path(request, **kw):
1955 def current_route_path(request, **kw):
1956 new_args = request.GET.mixed()
1956 new_args = request.GET.mixed()
1957 new_args.update(kw)
1957 new_args.update(kw)
1958 return request.current_route_path(_query=new_args)
1958 return request.current_route_path(_query=new_args)
1959
1959
1960
1960
1961 def curl_api_example(method, args):
1961 def curl_api_example(method, args):
1962 args_json = json.dumps(OrderedDict([
1962 args_json = json.dumps(OrderedDict([
1963 ('id', 1),
1963 ('id', 1),
1964 ('auth_token', 'SECRET'),
1964 ('auth_token', 'SECRET'),
1965 ('method', method),
1965 ('method', method),
1966 ('args', args)
1966 ('args', args)
1967 ]))
1967 ]))
1968
1968
1969 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
1969 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
1970 api_url=route_url('apiv2'),
1970 api_url=route_url('apiv2'),
1971 args_json=args_json
1971 args_json=args_json
1972 )
1972 )
1973
1973
1974
1974
1975 def api_call_example(method, args):
1975 def api_call_example(method, args):
1976 """
1976 """
1977 Generates an API call example via CURL
1977 Generates an API call example via CURL
1978 """
1978 """
1979 curl_call = curl_api_example(method, args)
1979 curl_call = curl_api_example(method, args)
1980
1980
1981 return literal(
1981 return literal(
1982 curl_call +
1982 curl_call +
1983 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1983 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1984 "and needs to be of `api calls` role."
1984 "and needs to be of `api calls` role."
1985 .format(token_url=route_url('my_account_auth_tokens')))
1985 .format(token_url=route_url('my_account_auth_tokens')))
1986
1986
1987
1987
1988 def notification_description(notification, request):
1988 def notification_description(notification, request):
1989 """
1989 """
1990 Generate notification human readable description based on notification type
1990 Generate notification human readable description based on notification type
1991 """
1991 """
1992 from rhodecode.model.notification import NotificationModel
1992 from rhodecode.model.notification import NotificationModel
1993 return NotificationModel().make_description(
1993 return NotificationModel().make_description(
1994 notification, translate=request.translate)
1994 notification, translate=request.translate)
1995
1995
1996
1996
1997 def go_import_header(request, db_repo=None):
1997 def go_import_header(request, db_repo=None):
1998 """
1998 """
1999 Creates a header for go-import functionality in Go Lang
1999 Creates a header for go-import functionality in Go Lang
2000 """
2000 """
2001
2001
2002 if not db_repo:
2002 if not db_repo:
2003 return
2003 return
2004 if 'go-get' not in request.GET:
2004 if 'go-get' not in request.GET:
2005 return
2005 return
2006
2006
2007 clone_url = db_repo.clone_url()
2007 clone_url = db_repo.clone_url()
2008 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2008 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2009 # we have a repo and go-get flag,
2009 # we have a repo and go-get flag,
2010 return literal('<meta name="go-import" content="{} {} {}">'.format(
2010 return literal('<meta name="go-import" content="{} {} {}">'.format(
2011 prefix, db_repo.repo_type, clone_url))
2011 prefix, db_repo.repo_type, clone_url))
2012
2012
2013
2013
2014 def reviewer_as_json(*args, **kwargs):
2014 def reviewer_as_json(*args, **kwargs):
2015 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2015 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2016 return _reviewer_as_json(*args, **kwargs)
2016 return _reviewer_as_json(*args, **kwargs)
2017
2017
2018
2018
2019 def get_repo_view_type(request):
2019 def get_repo_view_type(request):
2020 route_name = request.matched_route.name
2020 route_name = request.matched_route.name
2021 route_to_view_type = {
2021 route_to_view_type = {
2022 'repo_changelog': 'commits',
2022 'repo_changelog': 'commits',
2023 'repo_commits': 'commits',
2023 'repo_commits': 'commits',
2024 'repo_files': 'files',
2024 'repo_files': 'files',
2025 'repo_summary': 'summary',
2025 'repo_summary': 'summary',
2026 'repo_commit': 'commit'
2026 'repo_commit': 'commit'
2027 }
2027 }
2028
2028
2029 return route_to_view_type.get(route_name)
2029 return route_to_view_type.get(route_name)
2030
2030
2031
2031
2032 def is_active(menu_entry, selected):
2032 def is_active(menu_entry, selected):
2033 """
2033 """
2034 Returns active class for selecting menus in templates
2034 Returns active class for selecting menus in templates
2035 <li class=${h.is_active('settings', current_active)}></li>
2035 <li class=${h.is_active('settings', current_active)}></li>
2036 """
2036 """
2037 if not isinstance(menu_entry, list):
2037 if not isinstance(menu_entry, list):
2038 menu_entry = [menu_entry]
2038 menu_entry = [menu_entry]
2039
2039
2040 if selected in menu_entry:
2040 if selected in menu_entry:
2041 return "active"
2041 return "active"
@@ -1,5645 +1,5663 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers2.text import remove_formatting
55 from webhelpers2.text import remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 extra_sort_num = '1' # default
106 extra_sort_num = '1' # default
107
107
108 # NOTE(dan): inactive duplicates goes last
108 # NOTE(dan): inactive duplicates goes last
109 if getattr(obj, 'duplicate_perm', None):
109 if getattr(obj, 'duplicate_perm', None):
110 extra_sort_num = '9'
110 extra_sort_num = '9'
111 return prefix + extra_sort_num + obj.username
111 return prefix + extra_sort_num + obj.username
112
112
113
113
114 def display_user_group_sort(obj):
114 def display_user_group_sort(obj):
115 """
115 """
116 Sort function used to sort permissions in .permissions() function of
116 Sort function used to sort permissions in .permissions() function of
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
118 of all other resources
118 of all other resources
119 """
119 """
120
120
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
122 return prefix + obj.users_group_name
122 return prefix + obj.users_group_name
123
123
124
124
125 def _hash_key(k):
125 def _hash_key(k):
126 return sha1_safe(k)
126 return sha1_safe(k)
127
127
128
128
129 def in_filter_generator(qry, items, limit=500):
129 def in_filter_generator(qry, items, limit=500):
130 """
130 """
131 Splits IN() into multiple with OR
131 Splits IN() into multiple with OR
132 e.g.::
132 e.g.::
133 cnt = Repository.query().filter(
133 cnt = Repository.query().filter(
134 or_(
134 or_(
135 *in_filter_generator(Repository.repo_id, range(100000))
135 *in_filter_generator(Repository.repo_id, range(100000))
136 )).count()
136 )).count()
137 """
137 """
138 if not items:
138 if not items:
139 # empty list will cause empty query which might cause security issues
139 # empty list will cause empty query which might cause security issues
140 # this can lead to hidden unpleasant results
140 # this can lead to hidden unpleasant results
141 items = [-1]
141 items = [-1]
142
142
143 parts = []
143 parts = []
144 for chunk in xrange(0, len(items), limit):
144 for chunk in xrange(0, len(items), limit):
145 parts.append(
145 parts.append(
146 qry.in_(items[chunk: chunk + limit])
146 qry.in_(items[chunk: chunk + limit])
147 )
147 )
148
148
149 return parts
149 return parts
150
150
151
151
152 base_table_args = {
152 base_table_args = {
153 'extend_existing': True,
153 'extend_existing': True,
154 'mysql_engine': 'InnoDB',
154 'mysql_engine': 'InnoDB',
155 'mysql_charset': 'utf8',
155 'mysql_charset': 'utf8',
156 'sqlite_autoincrement': True
156 'sqlite_autoincrement': True
157 }
157 }
158
158
159
159
160 class EncryptedTextValue(TypeDecorator):
160 class EncryptedTextValue(TypeDecorator):
161 """
161 """
162 Special column for encrypted long text data, use like::
162 Special column for encrypted long text data, use like::
163
163
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
165
165
166 This column is intelligent so if value is in unencrypted form it return
166 This column is intelligent so if value is in unencrypted form it return
167 unencrypted form, but on save it always encrypts
167 unencrypted form, but on save it always encrypts
168 """
168 """
169 impl = Text
169 impl = Text
170
170
171 def process_bind_param(self, value, dialect):
171 def process_bind_param(self, value, dialect):
172 """
172 """
173 Setter for storing value
173 Setter for storing value
174 """
174 """
175 import rhodecode
175 import rhodecode
176 if not value:
176 if not value:
177 return value
177 return value
178
178
179 # protect against double encrypting if values is already encrypted
179 # protect against double encrypting if values is already encrypted
180 if value.startswith('enc$aes$') \
180 if value.startswith('enc$aes$') \
181 or value.startswith('enc$aes_hmac$') \
181 or value.startswith('enc$aes_hmac$') \
182 or value.startswith('enc2$'):
182 or value.startswith('enc2$'):
183 raise ValueError('value needs to be in unencrypted format, '
183 raise ValueError('value needs to be in unencrypted format, '
184 'ie. not starting with enc$ or enc2$')
184 'ie. not starting with enc$ or enc2$')
185
185
186 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
186 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
187 if algo == 'aes':
187 if algo == 'aes':
188 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
188 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
189 elif algo == 'fernet':
189 elif algo == 'fernet':
190 return Encryptor(ENCRYPTION_KEY).encrypt(value)
190 return Encryptor(ENCRYPTION_KEY).encrypt(value)
191 else:
191 else:
192 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
192 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
193
193
194 def process_result_value(self, value, dialect):
194 def process_result_value(self, value, dialect):
195 """
195 """
196 Getter for retrieving value
196 Getter for retrieving value
197 """
197 """
198
198
199 import rhodecode
199 import rhodecode
200 if not value:
200 if not value:
201 return value
201 return value
202
202
203 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
203 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
204 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
204 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
205 if algo == 'aes':
205 if algo == 'aes':
206 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
206 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
207 elif algo == 'fernet':
207 elif algo == 'fernet':
208 return Encryptor(ENCRYPTION_KEY).decrypt(value)
208 return Encryptor(ENCRYPTION_KEY).decrypt(value)
209 else:
209 else:
210 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
210 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
211 return decrypted_data
211 return decrypted_data
212
212
213
213
214 class BaseModel(object):
214 class BaseModel(object):
215 """
215 """
216 Base Model for all classes
216 Base Model for all classes
217 """
217 """
218
218
219 @classmethod
219 @classmethod
220 def _get_keys(cls):
220 def _get_keys(cls):
221 """return column names for this model """
221 """return column names for this model """
222 return class_mapper(cls).c.keys()
222 return class_mapper(cls).c.keys()
223
223
224 def get_dict(self):
224 def get_dict(self):
225 """
225 """
226 return dict with keys and values corresponding
226 return dict with keys and values corresponding
227 to this model data """
227 to this model data """
228
228
229 d = {}
229 d = {}
230 for k in self._get_keys():
230 for k in self._get_keys():
231 d[k] = getattr(self, k)
231 d[k] = getattr(self, k)
232
232
233 # also use __json__() if present to get additional fields
233 # also use __json__() if present to get additional fields
234 _json_attr = getattr(self, '__json__', None)
234 _json_attr = getattr(self, '__json__', None)
235 if _json_attr:
235 if _json_attr:
236 # update with attributes from __json__
236 # update with attributes from __json__
237 if callable(_json_attr):
237 if callable(_json_attr):
238 _json_attr = _json_attr()
238 _json_attr = _json_attr()
239 for k, val in _json_attr.iteritems():
239 for k, val in _json_attr.iteritems():
240 d[k] = val
240 d[k] = val
241 return d
241 return d
242
242
243 def get_appstruct(self):
243 def get_appstruct(self):
244 """return list with keys and values tuples corresponding
244 """return list with keys and values tuples corresponding
245 to this model data """
245 to this model data """
246
246
247 lst = []
247 lst = []
248 for k in self._get_keys():
248 for k in self._get_keys():
249 lst.append((k, getattr(self, k),))
249 lst.append((k, getattr(self, k),))
250 return lst
250 return lst
251
251
252 def populate_obj(self, populate_dict):
252 def populate_obj(self, populate_dict):
253 """populate model with data from given populate_dict"""
253 """populate model with data from given populate_dict"""
254
254
255 for k in self._get_keys():
255 for k in self._get_keys():
256 if k in populate_dict:
256 if k in populate_dict:
257 setattr(self, k, populate_dict[k])
257 setattr(self, k, populate_dict[k])
258
258
259 @classmethod
259 @classmethod
260 def query(cls):
260 def query(cls):
261 return Session().query(cls)
261 return Session().query(cls)
262
262
263 @classmethod
263 @classmethod
264 def get(cls, id_):
264 def get(cls, id_):
265 if id_:
265 if id_:
266 return cls.query().get(id_)
266 return cls.query().get(id_)
267
267
268 @classmethod
268 @classmethod
269 def get_or_404(cls, id_):
269 def get_or_404(cls, id_):
270 from pyramid.httpexceptions import HTTPNotFound
270 from pyramid.httpexceptions import HTTPNotFound
271
271
272 try:
272 try:
273 id_ = int(id_)
273 id_ = int(id_)
274 except (TypeError, ValueError):
274 except (TypeError, ValueError):
275 raise HTTPNotFound()
275 raise HTTPNotFound()
276
276
277 res = cls.query().get(id_)
277 res = cls.query().get(id_)
278 if not res:
278 if not res:
279 raise HTTPNotFound()
279 raise HTTPNotFound()
280 return res
280 return res
281
281
282 @classmethod
282 @classmethod
283 def getAll(cls):
283 def getAll(cls):
284 # deprecated and left for backward compatibility
284 # deprecated and left for backward compatibility
285 return cls.get_all()
285 return cls.get_all()
286
286
287 @classmethod
287 @classmethod
288 def get_all(cls):
288 def get_all(cls):
289 return cls.query().all()
289 return cls.query().all()
290
290
291 @classmethod
291 @classmethod
292 def delete(cls, id_):
292 def delete(cls, id_):
293 obj = cls.query().get(id_)
293 obj = cls.query().get(id_)
294 Session().delete(obj)
294 Session().delete(obj)
295
295
296 @classmethod
296 @classmethod
297 def identity_cache(cls, session, attr_name, value):
297 def identity_cache(cls, session, attr_name, value):
298 exist_in_session = []
298 exist_in_session = []
299 for (item_cls, pkey), instance in session.identity_map.items():
299 for (item_cls, pkey), instance in session.identity_map.items():
300 if cls == item_cls and getattr(instance, attr_name) == value:
300 if cls == item_cls and getattr(instance, attr_name) == value:
301 exist_in_session.append(instance)
301 exist_in_session.append(instance)
302 if exist_in_session:
302 if exist_in_session:
303 if len(exist_in_session) == 1:
303 if len(exist_in_session) == 1:
304 return exist_in_session[0]
304 return exist_in_session[0]
305 log.exception(
305 log.exception(
306 'multiple objects with attr %s and '
306 'multiple objects with attr %s and '
307 'value %s found with same name: %r',
307 'value %s found with same name: %r',
308 attr_name, value, exist_in_session)
308 attr_name, value, exist_in_session)
309
309
310 def __repr__(self):
310 def __repr__(self):
311 if hasattr(self, '__unicode__'):
311 if hasattr(self, '__unicode__'):
312 # python repr needs to return str
312 # python repr needs to return str
313 try:
313 try:
314 return safe_str(self.__unicode__())
314 return safe_str(self.__unicode__())
315 except UnicodeDecodeError:
315 except UnicodeDecodeError:
316 pass
316 pass
317 return '<DB:%s>' % (self.__class__.__name__)
317 return '<DB:%s>' % (self.__class__.__name__)
318
318
319
319
320 class RhodeCodeSetting(Base, BaseModel):
320 class RhodeCodeSetting(Base, BaseModel):
321 __tablename__ = 'rhodecode_settings'
321 __tablename__ = 'rhodecode_settings'
322 __table_args__ = (
322 __table_args__ = (
323 UniqueConstraint('app_settings_name'),
323 UniqueConstraint('app_settings_name'),
324 base_table_args
324 base_table_args
325 )
325 )
326
326
327 SETTINGS_TYPES = {
327 SETTINGS_TYPES = {
328 'str': safe_str,
328 'str': safe_str,
329 'int': safe_int,
329 'int': safe_int,
330 'unicode': safe_unicode,
330 'unicode': safe_unicode,
331 'bool': str2bool,
331 'bool': str2bool,
332 'list': functools.partial(aslist, sep=',')
332 'list': functools.partial(aslist, sep=',')
333 }
333 }
334 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
334 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
335 GLOBAL_CONF_KEY = 'app_settings'
335 GLOBAL_CONF_KEY = 'app_settings'
336
336
337 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
337 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
338 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
338 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
339 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
339 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
340 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
340 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
341
341
342 def __init__(self, key='', val='', type='unicode'):
342 def __init__(self, key='', val='', type='unicode'):
343 self.app_settings_name = key
343 self.app_settings_name = key
344 self.app_settings_type = type
344 self.app_settings_type = type
345 self.app_settings_value = val
345 self.app_settings_value = val
346
346
347 @validates('_app_settings_value')
347 @validates('_app_settings_value')
348 def validate_settings_value(self, key, val):
348 def validate_settings_value(self, key, val):
349 assert type(val) == unicode
349 assert type(val) == unicode
350 return val
350 return val
351
351
352 @hybrid_property
352 @hybrid_property
353 def app_settings_value(self):
353 def app_settings_value(self):
354 v = self._app_settings_value
354 v = self._app_settings_value
355 _type = self.app_settings_type
355 _type = self.app_settings_type
356 if _type:
356 if _type:
357 _type = self.app_settings_type.split('.')[0]
357 _type = self.app_settings_type.split('.')[0]
358 # decode the encrypted value
358 # decode the encrypted value
359 if 'encrypted' in self.app_settings_type:
359 if 'encrypted' in self.app_settings_type:
360 cipher = EncryptedTextValue()
360 cipher = EncryptedTextValue()
361 v = safe_unicode(cipher.process_result_value(v, None))
361 v = safe_unicode(cipher.process_result_value(v, None))
362
362
363 converter = self.SETTINGS_TYPES.get(_type) or \
363 converter = self.SETTINGS_TYPES.get(_type) or \
364 self.SETTINGS_TYPES['unicode']
364 self.SETTINGS_TYPES['unicode']
365 return converter(v)
365 return converter(v)
366
366
367 @app_settings_value.setter
367 @app_settings_value.setter
368 def app_settings_value(self, val):
368 def app_settings_value(self, val):
369 """
369 """
370 Setter that will always make sure we use unicode in app_settings_value
370 Setter that will always make sure we use unicode in app_settings_value
371
371
372 :param val:
372 :param val:
373 """
373 """
374 val = safe_unicode(val)
374 val = safe_unicode(val)
375 # encode the encrypted value
375 # encode the encrypted value
376 if 'encrypted' in self.app_settings_type:
376 if 'encrypted' in self.app_settings_type:
377 cipher = EncryptedTextValue()
377 cipher = EncryptedTextValue()
378 val = safe_unicode(cipher.process_bind_param(val, None))
378 val = safe_unicode(cipher.process_bind_param(val, None))
379 self._app_settings_value = val
379 self._app_settings_value = val
380
380
381 @hybrid_property
381 @hybrid_property
382 def app_settings_type(self):
382 def app_settings_type(self):
383 return self._app_settings_type
383 return self._app_settings_type
384
384
385 @app_settings_type.setter
385 @app_settings_type.setter
386 def app_settings_type(self, val):
386 def app_settings_type(self, val):
387 if val.split('.')[0] not in self.SETTINGS_TYPES:
387 if val.split('.')[0] not in self.SETTINGS_TYPES:
388 raise Exception('type must be one of %s got %s'
388 raise Exception('type must be one of %s got %s'
389 % (self.SETTINGS_TYPES.keys(), val))
389 % (self.SETTINGS_TYPES.keys(), val))
390 self._app_settings_type = val
390 self._app_settings_type = val
391
391
392 @classmethod
392 @classmethod
393 def get_by_prefix(cls, prefix):
393 def get_by_prefix(cls, prefix):
394 return RhodeCodeSetting.query()\
394 return RhodeCodeSetting.query()\
395 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
395 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
396 .all()
396 .all()
397
397
398 def __unicode__(self):
398 def __unicode__(self):
399 return u"<%s('%s:%s[%s]')>" % (
399 return u"<%s('%s:%s[%s]')>" % (
400 self.__class__.__name__,
400 self.__class__.__name__,
401 self.app_settings_name, self.app_settings_value,
401 self.app_settings_name, self.app_settings_value,
402 self.app_settings_type
402 self.app_settings_type
403 )
403 )
404
404
405
405
406 class RhodeCodeUi(Base, BaseModel):
406 class RhodeCodeUi(Base, BaseModel):
407 __tablename__ = 'rhodecode_ui'
407 __tablename__ = 'rhodecode_ui'
408 __table_args__ = (
408 __table_args__ = (
409 UniqueConstraint('ui_key'),
409 UniqueConstraint('ui_key'),
410 base_table_args
410 base_table_args
411 )
411 )
412
412
413 HOOK_REPO_SIZE = 'changegroup.repo_size'
413 HOOK_REPO_SIZE = 'changegroup.repo_size'
414 # HG
414 # HG
415 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
415 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
416 HOOK_PULL = 'outgoing.pull_logger'
416 HOOK_PULL = 'outgoing.pull_logger'
417 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
417 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
418 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
418 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
419 HOOK_PUSH = 'changegroup.push_logger'
419 HOOK_PUSH = 'changegroup.push_logger'
420 HOOK_PUSH_KEY = 'pushkey.key_push'
420 HOOK_PUSH_KEY = 'pushkey.key_push'
421
421
422 HOOKS_BUILTIN = [
422 HOOKS_BUILTIN = [
423 HOOK_PRE_PULL,
423 HOOK_PRE_PULL,
424 HOOK_PULL,
424 HOOK_PULL,
425 HOOK_PRE_PUSH,
425 HOOK_PRE_PUSH,
426 HOOK_PRETX_PUSH,
426 HOOK_PRETX_PUSH,
427 HOOK_PUSH,
427 HOOK_PUSH,
428 HOOK_PUSH_KEY,
428 HOOK_PUSH_KEY,
429 ]
429 ]
430
430
431 # TODO: johbo: Unify way how hooks are configured for git and hg,
431 # TODO: johbo: Unify way how hooks are configured for git and hg,
432 # git part is currently hardcoded.
432 # git part is currently hardcoded.
433
433
434 # SVN PATTERNS
434 # SVN PATTERNS
435 SVN_BRANCH_ID = 'vcs_svn_branch'
435 SVN_BRANCH_ID = 'vcs_svn_branch'
436 SVN_TAG_ID = 'vcs_svn_tag'
436 SVN_TAG_ID = 'vcs_svn_tag'
437
437
438 ui_id = Column(
438 ui_id = Column(
439 "ui_id", Integer(), nullable=False, unique=True, default=None,
439 "ui_id", Integer(), nullable=False, unique=True, default=None,
440 primary_key=True)
440 primary_key=True)
441 ui_section = Column(
441 ui_section = Column(
442 "ui_section", String(255), nullable=True, unique=None, default=None)
442 "ui_section", String(255), nullable=True, unique=None, default=None)
443 ui_key = Column(
443 ui_key = Column(
444 "ui_key", String(255), nullable=True, unique=None, default=None)
444 "ui_key", String(255), nullable=True, unique=None, default=None)
445 ui_value = Column(
445 ui_value = Column(
446 "ui_value", String(255), nullable=True, unique=None, default=None)
446 "ui_value", String(255), nullable=True, unique=None, default=None)
447 ui_active = Column(
447 ui_active = Column(
448 "ui_active", Boolean(), nullable=True, unique=None, default=True)
448 "ui_active", Boolean(), nullable=True, unique=None, default=True)
449
449
450 def __repr__(self):
450 def __repr__(self):
451 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
451 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
452 self.ui_key, self.ui_value)
452 self.ui_key, self.ui_value)
453
453
454
454
455 class RepoRhodeCodeSetting(Base, BaseModel):
455 class RepoRhodeCodeSetting(Base, BaseModel):
456 __tablename__ = 'repo_rhodecode_settings'
456 __tablename__ = 'repo_rhodecode_settings'
457 __table_args__ = (
457 __table_args__ = (
458 UniqueConstraint(
458 UniqueConstraint(
459 'app_settings_name', 'repository_id',
459 'app_settings_name', 'repository_id',
460 name='uq_repo_rhodecode_setting_name_repo_id'),
460 name='uq_repo_rhodecode_setting_name_repo_id'),
461 base_table_args
461 base_table_args
462 )
462 )
463
463
464 repository_id = Column(
464 repository_id = Column(
465 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
465 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
466 nullable=False)
466 nullable=False)
467 app_settings_id = Column(
467 app_settings_id = Column(
468 "app_settings_id", Integer(), nullable=False, unique=True,
468 "app_settings_id", Integer(), nullable=False, unique=True,
469 default=None, primary_key=True)
469 default=None, primary_key=True)
470 app_settings_name = Column(
470 app_settings_name = Column(
471 "app_settings_name", String(255), nullable=True, unique=None,
471 "app_settings_name", String(255), nullable=True, unique=None,
472 default=None)
472 default=None)
473 _app_settings_value = Column(
473 _app_settings_value = Column(
474 "app_settings_value", String(4096), nullable=True, unique=None,
474 "app_settings_value", String(4096), nullable=True, unique=None,
475 default=None)
475 default=None)
476 _app_settings_type = Column(
476 _app_settings_type = Column(
477 "app_settings_type", String(255), nullable=True, unique=None,
477 "app_settings_type", String(255), nullable=True, unique=None,
478 default=None)
478 default=None)
479
479
480 repository = relationship('Repository')
480 repository = relationship('Repository')
481
481
482 def __init__(self, repository_id, key='', val='', type='unicode'):
482 def __init__(self, repository_id, key='', val='', type='unicode'):
483 self.repository_id = repository_id
483 self.repository_id = repository_id
484 self.app_settings_name = key
484 self.app_settings_name = key
485 self.app_settings_type = type
485 self.app_settings_type = type
486 self.app_settings_value = val
486 self.app_settings_value = val
487
487
488 @validates('_app_settings_value')
488 @validates('_app_settings_value')
489 def validate_settings_value(self, key, val):
489 def validate_settings_value(self, key, val):
490 assert type(val) == unicode
490 assert type(val) == unicode
491 return val
491 return val
492
492
493 @hybrid_property
493 @hybrid_property
494 def app_settings_value(self):
494 def app_settings_value(self):
495 v = self._app_settings_value
495 v = self._app_settings_value
496 type_ = self.app_settings_type
496 type_ = self.app_settings_type
497 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
497 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
498 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
498 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
499 return converter(v)
499 return converter(v)
500
500
501 @app_settings_value.setter
501 @app_settings_value.setter
502 def app_settings_value(self, val):
502 def app_settings_value(self, val):
503 """
503 """
504 Setter that will always make sure we use unicode in app_settings_value
504 Setter that will always make sure we use unicode in app_settings_value
505
505
506 :param val:
506 :param val:
507 """
507 """
508 self._app_settings_value = safe_unicode(val)
508 self._app_settings_value = safe_unicode(val)
509
509
510 @hybrid_property
510 @hybrid_property
511 def app_settings_type(self):
511 def app_settings_type(self):
512 return self._app_settings_type
512 return self._app_settings_type
513
513
514 @app_settings_type.setter
514 @app_settings_type.setter
515 def app_settings_type(self, val):
515 def app_settings_type(self, val):
516 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
516 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
517 if val not in SETTINGS_TYPES:
517 if val not in SETTINGS_TYPES:
518 raise Exception('type must be one of %s got %s'
518 raise Exception('type must be one of %s got %s'
519 % (SETTINGS_TYPES.keys(), val))
519 % (SETTINGS_TYPES.keys(), val))
520 self._app_settings_type = val
520 self._app_settings_type = val
521
521
522 def __unicode__(self):
522 def __unicode__(self):
523 return u"<%s('%s:%s:%s[%s]')>" % (
523 return u"<%s('%s:%s:%s[%s]')>" % (
524 self.__class__.__name__, self.repository.repo_name,
524 self.__class__.__name__, self.repository.repo_name,
525 self.app_settings_name, self.app_settings_value,
525 self.app_settings_name, self.app_settings_value,
526 self.app_settings_type
526 self.app_settings_type
527 )
527 )
528
528
529
529
530 class RepoRhodeCodeUi(Base, BaseModel):
530 class RepoRhodeCodeUi(Base, BaseModel):
531 __tablename__ = 'repo_rhodecode_ui'
531 __tablename__ = 'repo_rhodecode_ui'
532 __table_args__ = (
532 __table_args__ = (
533 UniqueConstraint(
533 UniqueConstraint(
534 'repository_id', 'ui_section', 'ui_key',
534 'repository_id', 'ui_section', 'ui_key',
535 name='uq_repo_rhodecode_ui_repository_id_section_key'),
535 name='uq_repo_rhodecode_ui_repository_id_section_key'),
536 base_table_args
536 base_table_args
537 )
537 )
538
538
539 repository_id = Column(
539 repository_id = Column(
540 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
540 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
541 nullable=False)
541 nullable=False)
542 ui_id = Column(
542 ui_id = Column(
543 "ui_id", Integer(), nullable=False, unique=True, default=None,
543 "ui_id", Integer(), nullable=False, unique=True, default=None,
544 primary_key=True)
544 primary_key=True)
545 ui_section = Column(
545 ui_section = Column(
546 "ui_section", String(255), nullable=True, unique=None, default=None)
546 "ui_section", String(255), nullable=True, unique=None, default=None)
547 ui_key = Column(
547 ui_key = Column(
548 "ui_key", String(255), nullable=True, unique=None, default=None)
548 "ui_key", String(255), nullable=True, unique=None, default=None)
549 ui_value = Column(
549 ui_value = Column(
550 "ui_value", String(255), nullable=True, unique=None, default=None)
550 "ui_value", String(255), nullable=True, unique=None, default=None)
551 ui_active = Column(
551 ui_active = Column(
552 "ui_active", Boolean(), nullable=True, unique=None, default=True)
552 "ui_active", Boolean(), nullable=True, unique=None, default=True)
553
553
554 repository = relationship('Repository')
554 repository = relationship('Repository')
555
555
556 def __repr__(self):
556 def __repr__(self):
557 return '<%s[%s:%s]%s=>%s]>' % (
557 return '<%s[%s:%s]%s=>%s]>' % (
558 self.__class__.__name__, self.repository.repo_name,
558 self.__class__.__name__, self.repository.repo_name,
559 self.ui_section, self.ui_key, self.ui_value)
559 self.ui_section, self.ui_key, self.ui_value)
560
560
561
561
562 class User(Base, BaseModel):
562 class User(Base, BaseModel):
563 __tablename__ = 'users'
563 __tablename__ = 'users'
564 __table_args__ = (
564 __table_args__ = (
565 UniqueConstraint('username'), UniqueConstraint('email'),
565 UniqueConstraint('username'), UniqueConstraint('email'),
566 Index('u_username_idx', 'username'),
566 Index('u_username_idx', 'username'),
567 Index('u_email_idx', 'email'),
567 Index('u_email_idx', 'email'),
568 base_table_args
568 base_table_args
569 )
569 )
570
570
571 DEFAULT_USER = 'default'
571 DEFAULT_USER = 'default'
572 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
572 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
573 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
573 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
574
574
575 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
575 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
576 username = Column("username", String(255), nullable=True, unique=None, default=None)
576 username = Column("username", String(255), nullable=True, unique=None, default=None)
577 password = Column("password", String(255), nullable=True, unique=None, default=None)
577 password = Column("password", String(255), nullable=True, unique=None, default=None)
578 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
578 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
579 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
579 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
580 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
580 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
581 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
581 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
582 _email = Column("email", String(255), nullable=True, unique=None, default=None)
582 _email = Column("email", String(255), nullable=True, unique=None, default=None)
583 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
583 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
584 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
584 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
585 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
585 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
586
586
587 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
587 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
588 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
588 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
589 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
589 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
590 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
590 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
591 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
591 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
592 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
592 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
593
593
594 user_log = relationship('UserLog')
594 user_log = relationship('UserLog')
595 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
595 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
596
596
597 repositories = relationship('Repository')
597 repositories = relationship('Repository')
598 repository_groups = relationship('RepoGroup')
598 repository_groups = relationship('RepoGroup')
599 user_groups = relationship('UserGroup')
599 user_groups = relationship('UserGroup')
600
600
601 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
601 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
602 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
602 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
603
603
604 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
604 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
605 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
605 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
606 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
606 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
607
607
608 group_member = relationship('UserGroupMember', cascade='all')
608 group_member = relationship('UserGroupMember', cascade='all')
609
609
610 notifications = relationship('UserNotification', cascade='all')
610 notifications = relationship('UserNotification', cascade='all')
611 # notifications assigned to this user
611 # notifications assigned to this user
612 user_created_notifications = relationship('Notification', cascade='all')
612 user_created_notifications = relationship('Notification', cascade='all')
613 # comments created by this user
613 # comments created by this user
614 user_comments = relationship('ChangesetComment', cascade='all')
614 user_comments = relationship('ChangesetComment', cascade='all')
615 # user profile extra info
615 # user profile extra info
616 user_emails = relationship('UserEmailMap', cascade='all')
616 user_emails = relationship('UserEmailMap', cascade='all')
617 user_ip_map = relationship('UserIpMap', cascade='all')
617 user_ip_map = relationship('UserIpMap', cascade='all')
618 user_auth_tokens = relationship('UserApiKeys', cascade='all')
618 user_auth_tokens = relationship('UserApiKeys', cascade='all')
619 user_ssh_keys = relationship('UserSshKeys', cascade='all')
619 user_ssh_keys = relationship('UserSshKeys', cascade='all')
620
620
621 # gists
621 # gists
622 user_gists = relationship('Gist', cascade='all')
622 user_gists = relationship('Gist', cascade='all')
623 # user pull requests
623 # user pull requests
624 user_pull_requests = relationship('PullRequest', cascade='all')
624 user_pull_requests = relationship('PullRequest', cascade='all')
625
625
626 # external identities
626 # external identities
627 external_identities = relationship(
627 external_identities = relationship(
628 'ExternalIdentity',
628 'ExternalIdentity',
629 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
629 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
630 cascade='all')
630 cascade='all')
631 # review rules
631 # review rules
632 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
632 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
633
633
634 # artifacts owned
634 # artifacts owned
635 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
635 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
636
636
637 # no cascade, set NULL
637 # no cascade, set NULL
638 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
638 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
639
639
640 def __unicode__(self):
640 def __unicode__(self):
641 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
641 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
642 self.user_id, self.username)
642 self.user_id, self.username)
643
643
644 @hybrid_property
644 @hybrid_property
645 def email(self):
645 def email(self):
646 return self._email
646 return self._email
647
647
648 @email.setter
648 @email.setter
649 def email(self, val):
649 def email(self, val):
650 self._email = val.lower() if val else None
650 self._email = val.lower() if val else None
651
651
652 @hybrid_property
652 @hybrid_property
653 def first_name(self):
653 def first_name(self):
654 from rhodecode.lib import helpers as h
654 from rhodecode.lib import helpers as h
655 if self.name:
655 if self.name:
656 return h.escape(self.name)
656 return h.escape(self.name)
657 return self.name
657 return self.name
658
658
659 @hybrid_property
659 @hybrid_property
660 def last_name(self):
660 def last_name(self):
661 from rhodecode.lib import helpers as h
661 from rhodecode.lib import helpers as h
662 if self.lastname:
662 if self.lastname:
663 return h.escape(self.lastname)
663 return h.escape(self.lastname)
664 return self.lastname
664 return self.lastname
665
665
666 @hybrid_property
666 @hybrid_property
667 def api_key(self):
667 def api_key(self):
668 """
668 """
669 Fetch if exist an auth-token with role ALL connected to this user
669 Fetch if exist an auth-token with role ALL connected to this user
670 """
670 """
671 user_auth_token = UserApiKeys.query()\
671 user_auth_token = UserApiKeys.query()\
672 .filter(UserApiKeys.user_id == self.user_id)\
672 .filter(UserApiKeys.user_id == self.user_id)\
673 .filter(or_(UserApiKeys.expires == -1,
673 .filter(or_(UserApiKeys.expires == -1,
674 UserApiKeys.expires >= time.time()))\
674 UserApiKeys.expires >= time.time()))\
675 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
675 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
676 if user_auth_token:
676 if user_auth_token:
677 user_auth_token = user_auth_token.api_key
677 user_auth_token = user_auth_token.api_key
678
678
679 return user_auth_token
679 return user_auth_token
680
680
681 @api_key.setter
681 @api_key.setter
682 def api_key(self, val):
682 def api_key(self, val):
683 # don't allow to set API key this is deprecated for now
683 # don't allow to set API key this is deprecated for now
684 self._api_key = None
684 self._api_key = None
685
685
686 @property
686 @property
687 def reviewer_pull_requests(self):
687 def reviewer_pull_requests(self):
688 return PullRequestReviewers.query() \
688 return PullRequestReviewers.query() \
689 .options(joinedload(PullRequestReviewers.pull_request)) \
689 .options(joinedload(PullRequestReviewers.pull_request)) \
690 .filter(PullRequestReviewers.user_id == self.user_id) \
690 .filter(PullRequestReviewers.user_id == self.user_id) \
691 .all()
691 .all()
692
692
693 @property
693 @property
694 def firstname(self):
694 def firstname(self):
695 # alias for future
695 # alias for future
696 return self.name
696 return self.name
697
697
698 @property
698 @property
699 def emails(self):
699 def emails(self):
700 other = UserEmailMap.query()\
700 other = UserEmailMap.query()\
701 .filter(UserEmailMap.user == self) \
701 .filter(UserEmailMap.user == self) \
702 .order_by(UserEmailMap.email_id.asc()) \
702 .order_by(UserEmailMap.email_id.asc()) \
703 .all()
703 .all()
704 return [self.email] + [x.email for x in other]
704 return [self.email] + [x.email for x in other]
705
705
706 def emails_cached(self):
706 def emails_cached(self):
707 emails = UserEmailMap.query()\
707 emails = UserEmailMap.query()\
708 .filter(UserEmailMap.user == self) \
708 .filter(UserEmailMap.user == self) \
709 .order_by(UserEmailMap.email_id.asc())
709 .order_by(UserEmailMap.email_id.asc())
710
710
711 emails = emails.options(
711 emails = emails.options(
712 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
712 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
713 )
713 )
714
714
715 return [self.email] + [x.email for x in emails]
715 return [self.email] + [x.email for x in emails]
716
716
717 @property
717 @property
718 def auth_tokens(self):
718 def auth_tokens(self):
719 auth_tokens = self.get_auth_tokens()
719 auth_tokens = self.get_auth_tokens()
720 return [x.api_key for x in auth_tokens]
720 return [x.api_key for x in auth_tokens]
721
721
722 def get_auth_tokens(self):
722 def get_auth_tokens(self):
723 return UserApiKeys.query()\
723 return UserApiKeys.query()\
724 .filter(UserApiKeys.user == self)\
724 .filter(UserApiKeys.user == self)\
725 .order_by(UserApiKeys.user_api_key_id.asc())\
725 .order_by(UserApiKeys.user_api_key_id.asc())\
726 .all()
726 .all()
727
727
728 @LazyProperty
728 @LazyProperty
729 def feed_token(self):
729 def feed_token(self):
730 return self.get_feed_token()
730 return self.get_feed_token()
731
731
732 def get_feed_token(self, cache=True):
732 def get_feed_token(self, cache=True):
733 feed_tokens = UserApiKeys.query()\
733 feed_tokens = UserApiKeys.query()\
734 .filter(UserApiKeys.user == self)\
734 .filter(UserApiKeys.user == self)\
735 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
735 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
736 if cache:
736 if cache:
737 feed_tokens = feed_tokens.options(
737 feed_tokens = feed_tokens.options(
738 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
738 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
739
739
740 feed_tokens = feed_tokens.all()
740 feed_tokens = feed_tokens.all()
741 if feed_tokens:
741 if feed_tokens:
742 return feed_tokens[0].api_key
742 return feed_tokens[0].api_key
743 return 'NO_FEED_TOKEN_AVAILABLE'
743 return 'NO_FEED_TOKEN_AVAILABLE'
744
744
745 @LazyProperty
745 @LazyProperty
746 def artifact_token(self):
746 def artifact_token(self):
747 return self.get_artifact_token()
747 return self.get_artifact_token()
748
748
749 def get_artifact_token(self, cache=True):
749 def get_artifact_token(self, cache=True):
750 artifacts_tokens = UserApiKeys.query()\
750 artifacts_tokens = UserApiKeys.query()\
751 .filter(UserApiKeys.user == self)\
751 .filter(UserApiKeys.user == self)\
752 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
752 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
753 if cache:
753 if cache:
754 artifacts_tokens = artifacts_tokens.options(
754 artifacts_tokens = artifacts_tokens.options(
755 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
755 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
756
756
757 artifacts_tokens = artifacts_tokens.all()
757 artifacts_tokens = artifacts_tokens.all()
758 if artifacts_tokens:
758 if artifacts_tokens:
759 return artifacts_tokens[0].api_key
759 return artifacts_tokens[0].api_key
760 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
760 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
761
761
762 @classmethod
762 @classmethod
763 def get(cls, user_id, cache=False):
763 def get(cls, user_id, cache=False):
764 if not user_id:
764 if not user_id:
765 return
765 return
766
766
767 user = cls.query()
767 user = cls.query()
768 if cache:
768 if cache:
769 user = user.options(
769 user = user.options(
770 FromCache("sql_cache_short", "get_users_%s" % user_id))
770 FromCache("sql_cache_short", "get_users_%s" % user_id))
771 return user.get(user_id)
771 return user.get(user_id)
772
772
773 @classmethod
773 @classmethod
774 def extra_valid_auth_tokens(cls, user, role=None):
774 def extra_valid_auth_tokens(cls, user, role=None):
775 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
775 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
776 .filter(or_(UserApiKeys.expires == -1,
776 .filter(or_(UserApiKeys.expires == -1,
777 UserApiKeys.expires >= time.time()))
777 UserApiKeys.expires >= time.time()))
778 if role:
778 if role:
779 tokens = tokens.filter(or_(UserApiKeys.role == role,
779 tokens = tokens.filter(or_(UserApiKeys.role == role,
780 UserApiKeys.role == UserApiKeys.ROLE_ALL))
780 UserApiKeys.role == UserApiKeys.ROLE_ALL))
781 return tokens.all()
781 return tokens.all()
782
782
783 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
783 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
784 from rhodecode.lib import auth
784 from rhodecode.lib import auth
785
785
786 log.debug('Trying to authenticate user: %s via auth-token, '
786 log.debug('Trying to authenticate user: %s via auth-token, '
787 'and roles: %s', self, roles)
787 'and roles: %s', self, roles)
788
788
789 if not auth_token:
789 if not auth_token:
790 return False
790 return False
791
791
792 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
792 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
793 tokens_q = UserApiKeys.query()\
793 tokens_q = UserApiKeys.query()\
794 .filter(UserApiKeys.user_id == self.user_id)\
794 .filter(UserApiKeys.user_id == self.user_id)\
795 .filter(or_(UserApiKeys.expires == -1,
795 .filter(or_(UserApiKeys.expires == -1,
796 UserApiKeys.expires >= time.time()))
796 UserApiKeys.expires >= time.time()))
797
797
798 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
798 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
799
799
800 crypto_backend = auth.crypto_backend()
800 crypto_backend = auth.crypto_backend()
801 enc_token_map = {}
801 enc_token_map = {}
802 plain_token_map = {}
802 plain_token_map = {}
803 for token in tokens_q:
803 for token in tokens_q:
804 if token.api_key.startswith(crypto_backend.ENC_PREF):
804 if token.api_key.startswith(crypto_backend.ENC_PREF):
805 enc_token_map[token.api_key] = token
805 enc_token_map[token.api_key] = token
806 else:
806 else:
807 plain_token_map[token.api_key] = token
807 plain_token_map[token.api_key] = token
808 log.debug(
808 log.debug(
809 'Found %s plain and %s encrypted tokens to check for authentication for this user',
809 'Found %s plain and %s encrypted tokens to check for authentication for this user',
810 len(plain_token_map), len(enc_token_map))
810 len(plain_token_map), len(enc_token_map))
811
811
812 # plain token match comes first
812 # plain token match comes first
813 match = plain_token_map.get(auth_token)
813 match = plain_token_map.get(auth_token)
814
814
815 # check encrypted tokens now
815 # check encrypted tokens now
816 if not match:
816 if not match:
817 for token_hash, token in enc_token_map.items():
817 for token_hash, token in enc_token_map.items():
818 # NOTE(marcink): this is expensive to calculate, but most secure
818 # NOTE(marcink): this is expensive to calculate, but most secure
819 if crypto_backend.hash_check(auth_token, token_hash):
819 if crypto_backend.hash_check(auth_token, token_hash):
820 match = token
820 match = token
821 break
821 break
822
822
823 if match:
823 if match:
824 log.debug('Found matching token %s', match)
824 log.debug('Found matching token %s', match)
825 if match.repo_id:
825 if match.repo_id:
826 log.debug('Found scope, checking for scope match of token %s', match)
826 log.debug('Found scope, checking for scope match of token %s', match)
827 if match.repo_id == scope_repo_id:
827 if match.repo_id == scope_repo_id:
828 return True
828 return True
829 else:
829 else:
830 log.debug(
830 log.debug(
831 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
831 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
832 'and calling scope is:%s, skipping further checks',
832 'and calling scope is:%s, skipping further checks',
833 match.repo, scope_repo_id)
833 match.repo, scope_repo_id)
834 return False
834 return False
835 else:
835 else:
836 return True
836 return True
837
837
838 return False
838 return False
839
839
840 @property
840 @property
841 def ip_addresses(self):
841 def ip_addresses(self):
842 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
842 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
843 return [x.ip_addr for x in ret]
843 return [x.ip_addr for x in ret]
844
844
845 @property
845 @property
846 def username_and_name(self):
846 def username_and_name(self):
847 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
847 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
848
848
849 @property
849 @property
850 def username_or_name_or_email(self):
850 def username_or_name_or_email(self):
851 full_name = self.full_name if self.full_name is not ' ' else None
851 full_name = self.full_name if self.full_name is not ' ' else None
852 return self.username or full_name or self.email
852 return self.username or full_name or self.email
853
853
854 @property
854 @property
855 def full_name(self):
855 def full_name(self):
856 return '%s %s' % (self.first_name, self.last_name)
856 return '%s %s' % (self.first_name, self.last_name)
857
857
858 @property
858 @property
859 def full_name_or_username(self):
859 def full_name_or_username(self):
860 return ('%s %s' % (self.first_name, self.last_name)
860 return ('%s %s' % (self.first_name, self.last_name)
861 if (self.first_name and self.last_name) else self.username)
861 if (self.first_name and self.last_name) else self.username)
862
862
863 @property
863 @property
864 def full_contact(self):
864 def full_contact(self):
865 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
865 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
866
866
867 @property
867 @property
868 def short_contact(self):
868 def short_contact(self):
869 return '%s %s' % (self.first_name, self.last_name)
869 return '%s %s' % (self.first_name, self.last_name)
870
870
871 @property
871 @property
872 def is_admin(self):
872 def is_admin(self):
873 return self.admin
873 return self.admin
874
874
875 @property
875 @property
876 def language(self):
876 def language(self):
877 return self.user_data.get('language')
877 return self.user_data.get('language')
878
878
879 def AuthUser(self, **kwargs):
879 def AuthUser(self, **kwargs):
880 """
880 """
881 Returns instance of AuthUser for this user
881 Returns instance of AuthUser for this user
882 """
882 """
883 from rhodecode.lib.auth import AuthUser
883 from rhodecode.lib.auth import AuthUser
884 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
884 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
885
885
886 @hybrid_property
886 @hybrid_property
887 def user_data(self):
887 def user_data(self):
888 if not self._user_data:
888 if not self._user_data:
889 return {}
889 return {}
890
890
891 try:
891 try:
892 return json.loads(self._user_data)
892 return json.loads(self._user_data)
893 except TypeError:
893 except TypeError:
894 return {}
894 return {}
895
895
896 @user_data.setter
896 @user_data.setter
897 def user_data(self, val):
897 def user_data(self, val):
898 if not isinstance(val, dict):
898 if not isinstance(val, dict):
899 raise Exception('user_data must be dict, got %s' % type(val))
899 raise Exception('user_data must be dict, got %s' % type(val))
900 try:
900 try:
901 self._user_data = json.dumps(val)
901 self._user_data = json.dumps(val)
902 except Exception:
902 except Exception:
903 log.error(traceback.format_exc())
903 log.error(traceback.format_exc())
904
904
905 @classmethod
905 @classmethod
906 def get_by_username(cls, username, case_insensitive=False,
906 def get_by_username(cls, username, case_insensitive=False,
907 cache=False, identity_cache=False):
907 cache=False, identity_cache=False):
908 session = Session()
908 session = Session()
909
909
910 if case_insensitive:
910 if case_insensitive:
911 q = cls.query().filter(
911 q = cls.query().filter(
912 func.lower(cls.username) == func.lower(username))
912 func.lower(cls.username) == func.lower(username))
913 else:
913 else:
914 q = cls.query().filter(cls.username == username)
914 q = cls.query().filter(cls.username == username)
915
915
916 if cache:
916 if cache:
917 if identity_cache:
917 if identity_cache:
918 val = cls.identity_cache(session, 'username', username)
918 val = cls.identity_cache(session, 'username', username)
919 if val:
919 if val:
920 return val
920 return val
921 else:
921 else:
922 cache_key = "get_user_by_name_%s" % _hash_key(username)
922 cache_key = "get_user_by_name_%s" % _hash_key(username)
923 q = q.options(
923 q = q.options(
924 FromCache("sql_cache_short", cache_key))
924 FromCache("sql_cache_short", cache_key))
925
925
926 return q.scalar()
926 return q.scalar()
927
927
928 @classmethod
928 @classmethod
929 def get_by_auth_token(cls, auth_token, cache=False):
929 def get_by_auth_token(cls, auth_token, cache=False):
930 q = UserApiKeys.query()\
930 q = UserApiKeys.query()\
931 .filter(UserApiKeys.api_key == auth_token)\
931 .filter(UserApiKeys.api_key == auth_token)\
932 .filter(or_(UserApiKeys.expires == -1,
932 .filter(or_(UserApiKeys.expires == -1,
933 UserApiKeys.expires >= time.time()))
933 UserApiKeys.expires >= time.time()))
934 if cache:
934 if cache:
935 q = q.options(
935 q = q.options(
936 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
936 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
937
937
938 match = q.first()
938 match = q.first()
939 if match:
939 if match:
940 return match.user
940 return match.user
941
941
942 @classmethod
942 @classmethod
943 def get_by_email(cls, email, case_insensitive=False, cache=False):
943 def get_by_email(cls, email, case_insensitive=False, cache=False):
944
944
945 if case_insensitive:
945 if case_insensitive:
946 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
946 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
947
947
948 else:
948 else:
949 q = cls.query().filter(cls.email == email)
949 q = cls.query().filter(cls.email == email)
950
950
951 email_key = _hash_key(email)
951 email_key = _hash_key(email)
952 if cache:
952 if cache:
953 q = q.options(
953 q = q.options(
954 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
954 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
955
955
956 ret = q.scalar()
956 ret = q.scalar()
957 if ret is None:
957 if ret is None:
958 q = UserEmailMap.query()
958 q = UserEmailMap.query()
959 # try fetching in alternate email map
959 # try fetching in alternate email map
960 if case_insensitive:
960 if case_insensitive:
961 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
961 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
962 else:
962 else:
963 q = q.filter(UserEmailMap.email == email)
963 q = q.filter(UserEmailMap.email == email)
964 q = q.options(joinedload(UserEmailMap.user))
964 q = q.options(joinedload(UserEmailMap.user))
965 if cache:
965 if cache:
966 q = q.options(
966 q = q.options(
967 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
967 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
968 ret = getattr(q.scalar(), 'user', None)
968 ret = getattr(q.scalar(), 'user', None)
969
969
970 return ret
970 return ret
971
971
972 @classmethod
972 @classmethod
973 def get_from_cs_author(cls, author):
973 def get_from_cs_author(cls, author):
974 """
974 """
975 Tries to get User objects out of commit author string
975 Tries to get User objects out of commit author string
976
976
977 :param author:
977 :param author:
978 """
978 """
979 from rhodecode.lib.helpers import email, author_name
979 from rhodecode.lib.helpers import email, author_name
980 # Valid email in the attribute passed, see if they're in the system
980 # Valid email in the attribute passed, see if they're in the system
981 _email = email(author)
981 _email = email(author)
982 if _email:
982 if _email:
983 user = cls.get_by_email(_email, case_insensitive=True)
983 user = cls.get_by_email(_email, case_insensitive=True)
984 if user:
984 if user:
985 return user
985 return user
986 # Maybe we can match by username?
986 # Maybe we can match by username?
987 _author = author_name(author)
987 _author = author_name(author)
988 user = cls.get_by_username(_author, case_insensitive=True)
988 user = cls.get_by_username(_author, case_insensitive=True)
989 if user:
989 if user:
990 return user
990 return user
991
991
992 def update_userdata(self, **kwargs):
992 def update_userdata(self, **kwargs):
993 usr = self
993 usr = self
994 old = usr.user_data
994 old = usr.user_data
995 old.update(**kwargs)
995 old.update(**kwargs)
996 usr.user_data = old
996 usr.user_data = old
997 Session().add(usr)
997 Session().add(usr)
998 log.debug('updated userdata with %s', kwargs)
998 log.debug('updated userdata with %s', kwargs)
999
999
1000 def update_lastlogin(self):
1000 def update_lastlogin(self):
1001 """Update user lastlogin"""
1001 """Update user lastlogin"""
1002 self.last_login = datetime.datetime.now()
1002 self.last_login = datetime.datetime.now()
1003 Session().add(self)
1003 Session().add(self)
1004 log.debug('updated user %s lastlogin', self.username)
1004 log.debug('updated user %s lastlogin', self.username)
1005
1005
1006 def update_password(self, new_password):
1006 def update_password(self, new_password):
1007 from rhodecode.lib.auth import get_crypt_password
1007 from rhodecode.lib.auth import get_crypt_password
1008
1008
1009 self.password = get_crypt_password(new_password)
1009 self.password = get_crypt_password(new_password)
1010 Session().add(self)
1010 Session().add(self)
1011
1011
1012 @classmethod
1012 @classmethod
1013 def get_first_super_admin(cls):
1013 def get_first_super_admin(cls):
1014 user = User.query()\
1014 user = User.query()\
1015 .filter(User.admin == true()) \
1015 .filter(User.admin == true()) \
1016 .order_by(User.user_id.asc()) \
1016 .order_by(User.user_id.asc()) \
1017 .first()
1017 .first()
1018
1018
1019 if user is None:
1019 if user is None:
1020 raise Exception('FATAL: Missing administrative account!')
1020 raise Exception('FATAL: Missing administrative account!')
1021 return user
1021 return user
1022
1022
1023 @classmethod
1023 @classmethod
1024 def get_all_super_admins(cls, only_active=False):
1024 def get_all_super_admins(cls, only_active=False):
1025 """
1025 """
1026 Returns all admin accounts sorted by username
1026 Returns all admin accounts sorted by username
1027 """
1027 """
1028 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1028 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1029 if only_active:
1029 if only_active:
1030 qry = qry.filter(User.active == true())
1030 qry = qry.filter(User.active == true())
1031 return qry.all()
1031 return qry.all()
1032
1032
1033 @classmethod
1033 @classmethod
1034 def get_all_user_ids(cls, only_active=True):
1034 def get_all_user_ids(cls, only_active=True):
1035 """
1035 """
1036 Returns all users IDs
1036 Returns all users IDs
1037 """
1037 """
1038 qry = Session().query(User.user_id)
1038 qry = Session().query(User.user_id)
1039
1039
1040 if only_active:
1040 if only_active:
1041 qry = qry.filter(User.active == true())
1041 qry = qry.filter(User.active == true())
1042 return [x.user_id for x in qry]
1042 return [x.user_id for x in qry]
1043
1043
1044 @classmethod
1044 @classmethod
1045 def get_default_user(cls, cache=False, refresh=False):
1045 def get_default_user(cls, cache=False, refresh=False):
1046 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1046 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1047 if user is None:
1047 if user is None:
1048 raise Exception('FATAL: Missing default account!')
1048 raise Exception('FATAL: Missing default account!')
1049 if refresh:
1049 if refresh:
1050 # The default user might be based on outdated state which
1050 # The default user might be based on outdated state which
1051 # has been loaded from the cache.
1051 # has been loaded from the cache.
1052 # A call to refresh() ensures that the
1052 # A call to refresh() ensures that the
1053 # latest state from the database is used.
1053 # latest state from the database is used.
1054 Session().refresh(user)
1054 Session().refresh(user)
1055 return user
1055 return user
1056
1056
1057 @classmethod
1057 @classmethod
1058 def get_default_user_id(cls):
1058 def get_default_user_id(cls):
1059 import rhodecode
1059 import rhodecode
1060 return rhodecode.CONFIG['default_user_id']
1060 return rhodecode.CONFIG['default_user_id']
1061
1061
1062 def _get_default_perms(self, user, suffix=''):
1062 def _get_default_perms(self, user, suffix=''):
1063 from rhodecode.model.permission import PermissionModel
1063 from rhodecode.model.permission import PermissionModel
1064 return PermissionModel().get_default_perms(user.user_perms, suffix)
1064 return PermissionModel().get_default_perms(user.user_perms, suffix)
1065
1065
1066 def get_default_perms(self, suffix=''):
1066 def get_default_perms(self, suffix=''):
1067 return self._get_default_perms(self, suffix)
1067 return self._get_default_perms(self, suffix)
1068
1068
1069 def get_api_data(self, include_secrets=False, details='full'):
1069 def get_api_data(self, include_secrets=False, details='full'):
1070 """
1070 """
1071 Common function for generating user related data for API
1071 Common function for generating user related data for API
1072
1072
1073 :param include_secrets: By default secrets in the API data will be replaced
1073 :param include_secrets: By default secrets in the API data will be replaced
1074 by a placeholder value to prevent exposing this data by accident. In case
1074 by a placeholder value to prevent exposing this data by accident. In case
1075 this data shall be exposed, set this flag to ``True``.
1075 this data shall be exposed, set this flag to ``True``.
1076
1076
1077 :param details: details can be 'basic|full' basic gives only a subset of
1077 :param details: details can be 'basic|full' basic gives only a subset of
1078 the available user information that includes user_id, name and emails.
1078 the available user information that includes user_id, name and emails.
1079 """
1079 """
1080 user = self
1080 user = self
1081 user_data = self.user_data
1081 user_data = self.user_data
1082 data = {
1082 data = {
1083 'user_id': user.user_id,
1083 'user_id': user.user_id,
1084 'username': user.username,
1084 'username': user.username,
1085 'firstname': user.name,
1085 'firstname': user.name,
1086 'lastname': user.lastname,
1086 'lastname': user.lastname,
1087 'description': user.description,
1087 'description': user.description,
1088 'email': user.email,
1088 'email': user.email,
1089 'emails': user.emails,
1089 'emails': user.emails,
1090 }
1090 }
1091 if details == 'basic':
1091 if details == 'basic':
1092 return data
1092 return data
1093
1093
1094 auth_token_length = 40
1094 auth_token_length = 40
1095 auth_token_replacement = '*' * auth_token_length
1095 auth_token_replacement = '*' * auth_token_length
1096
1096
1097 extras = {
1097 extras = {
1098 'auth_tokens': [auth_token_replacement],
1098 'auth_tokens': [auth_token_replacement],
1099 'active': user.active,
1099 'active': user.active,
1100 'admin': user.admin,
1100 'admin': user.admin,
1101 'extern_type': user.extern_type,
1101 'extern_type': user.extern_type,
1102 'extern_name': user.extern_name,
1102 'extern_name': user.extern_name,
1103 'last_login': user.last_login,
1103 'last_login': user.last_login,
1104 'last_activity': user.last_activity,
1104 'last_activity': user.last_activity,
1105 'ip_addresses': user.ip_addresses,
1105 'ip_addresses': user.ip_addresses,
1106 'language': user_data.get('language')
1106 'language': user_data.get('language')
1107 }
1107 }
1108 data.update(extras)
1108 data.update(extras)
1109
1109
1110 if include_secrets:
1110 if include_secrets:
1111 data['auth_tokens'] = user.auth_tokens
1111 data['auth_tokens'] = user.auth_tokens
1112 return data
1112 return data
1113
1113
1114 def __json__(self):
1114 def __json__(self):
1115 data = {
1115 data = {
1116 'full_name': self.full_name,
1116 'full_name': self.full_name,
1117 'full_name_or_username': self.full_name_or_username,
1117 'full_name_or_username': self.full_name_or_username,
1118 'short_contact': self.short_contact,
1118 'short_contact': self.short_contact,
1119 'full_contact': self.full_contact,
1119 'full_contact': self.full_contact,
1120 }
1120 }
1121 data.update(self.get_api_data())
1121 data.update(self.get_api_data())
1122 return data
1122 return data
1123
1123
1124
1124
1125 class UserApiKeys(Base, BaseModel):
1125 class UserApiKeys(Base, BaseModel):
1126 __tablename__ = 'user_api_keys'
1126 __tablename__ = 'user_api_keys'
1127 __table_args__ = (
1127 __table_args__ = (
1128 Index('uak_api_key_idx', 'api_key'),
1128 Index('uak_api_key_idx', 'api_key'),
1129 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1129 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1130 base_table_args
1130 base_table_args
1131 )
1131 )
1132 __mapper_args__ = {}
1132 __mapper_args__ = {}
1133
1133
1134 # ApiKey role
1134 # ApiKey role
1135 ROLE_ALL = 'token_role_all'
1135 ROLE_ALL = 'token_role_all'
1136 ROLE_HTTP = 'token_role_http'
1137 ROLE_VCS = 'token_role_vcs'
1136 ROLE_VCS = 'token_role_vcs'
1138 ROLE_API = 'token_role_api'
1137 ROLE_API = 'token_role_api'
1138 ROLE_HTTP = 'token_role_http'
1139 ROLE_FEED = 'token_role_feed'
1139 ROLE_FEED = 'token_role_feed'
1140 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1140 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1141 # The last one is ignored in the list as we only
1142 # use it for one action, and cannot be created by users
1141 ROLE_PASSWORD_RESET = 'token_password_reset'
1143 ROLE_PASSWORD_RESET = 'token_password_reset'
1142
1144
1143 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1145 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1144
1146
1145 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1147 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1148 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1147 api_key = Column("api_key", String(255), nullable=False, unique=True)
1149 api_key = Column("api_key", String(255), nullable=False, unique=True)
1148 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1150 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1149 expires = Column('expires', Float(53), nullable=False)
1151 expires = Column('expires', Float(53), nullable=False)
1150 role = Column('role', String(255), nullable=True)
1152 role = Column('role', String(255), nullable=True)
1151 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1153 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1152
1154
1153 # scope columns
1155 # scope columns
1154 repo_id = Column(
1156 repo_id = Column(
1155 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1157 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1156 nullable=True, unique=None, default=None)
1158 nullable=True, unique=None, default=None)
1157 repo = relationship('Repository', lazy='joined')
1159 repo = relationship('Repository', lazy='joined')
1158
1160
1159 repo_group_id = Column(
1161 repo_group_id = Column(
1160 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1162 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1161 nullable=True, unique=None, default=None)
1163 nullable=True, unique=None, default=None)
1162 repo_group = relationship('RepoGroup', lazy='joined')
1164 repo_group = relationship('RepoGroup', lazy='joined')
1163
1165
1164 user = relationship('User', lazy='joined')
1166 user = relationship('User', lazy='joined')
1165
1167
1166 def __unicode__(self):
1168 def __unicode__(self):
1167 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1169 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1168
1170
1169 def __json__(self):
1171 def __json__(self):
1170 data = {
1172 data = {
1171 'auth_token': self.api_key,
1173 'auth_token': self.api_key,
1172 'role': self.role,
1174 'role': self.role,
1173 'scope': self.scope_humanized,
1175 'scope': self.scope_humanized,
1174 'expired': self.expired
1176 'expired': self.expired
1175 }
1177 }
1176 return data
1178 return data
1177
1179
1178 def get_api_data(self, include_secrets=False):
1180 def get_api_data(self, include_secrets=False):
1179 data = self.__json__()
1181 data = self.__json__()
1180 if include_secrets:
1182 if include_secrets:
1181 return data
1183 return data
1182 else:
1184 else:
1183 data['auth_token'] = self.token_obfuscated
1185 data['auth_token'] = self.token_obfuscated
1184 return data
1186 return data
1185
1187
1186 @hybrid_property
1188 @hybrid_property
1187 def description_safe(self):
1189 def description_safe(self):
1188 from rhodecode.lib import helpers as h
1190 from rhodecode.lib import helpers as h
1189 return h.escape(self.description)
1191 return h.escape(self.description)
1190
1192
1191 @property
1193 @property
1192 def expired(self):
1194 def expired(self):
1193 if self.expires == -1:
1195 if self.expires == -1:
1194 return False
1196 return False
1195 return time.time() > self.expires
1197 return time.time() > self.expires
1196
1198
1197 @classmethod
1199 @classmethod
1198 def _get_role_name(cls, role):
1200 def _get_role_name(cls, role):
1199 return {
1201 return {
1200 cls.ROLE_ALL: _('all'),
1202 cls.ROLE_ALL: _('all'),
1201 cls.ROLE_HTTP: _('http/web interface'),
1203 cls.ROLE_HTTP: _('http/web interface'),
1202 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1204 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1203 cls.ROLE_API: _('api calls'),
1205 cls.ROLE_API: _('api calls'),
1204 cls.ROLE_FEED: _('feed access'),
1206 cls.ROLE_FEED: _('feed access'),
1205 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1207 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1206 }.get(role, role)
1208 }.get(role, role)
1207
1209
1210 @classmethod
1211 def _get_role_description(cls, role):
1212 return {
1213 cls.ROLE_ALL: _('Token for all actions.'),
1214 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1215 'login using `api_access_controllers_whitelist` functionality.'),
1216 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1217 'Requires auth_token authentication plugin to be active. <br/>'
1218 'Such Token should be used then instead of a password to '
1219 'interact with a repository, and additionally can be '
1220 'limited to single repository using repo scope.'),
1221 cls.ROLE_API: _('Token limited to api calls.'),
1222 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1223 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1224 }.get(role, role)
1225
1208 @property
1226 @property
1209 def role_humanized(self):
1227 def role_humanized(self):
1210 return self._get_role_name(self.role)
1228 return self._get_role_name(self.role)
1211
1229
1212 def _get_scope(self):
1230 def _get_scope(self):
1213 if self.repo:
1231 if self.repo:
1214 return 'Repository: {}'.format(self.repo.repo_name)
1232 return 'Repository: {}'.format(self.repo.repo_name)
1215 if self.repo_group:
1233 if self.repo_group:
1216 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1234 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1217 return 'Global'
1235 return 'Global'
1218
1236
1219 @property
1237 @property
1220 def scope_humanized(self):
1238 def scope_humanized(self):
1221 return self._get_scope()
1239 return self._get_scope()
1222
1240
1223 @property
1241 @property
1224 def token_obfuscated(self):
1242 def token_obfuscated(self):
1225 if self.api_key:
1243 if self.api_key:
1226 return self.api_key[:4] + "****"
1244 return self.api_key[:4] + "****"
1227
1245
1228
1246
1229 class UserEmailMap(Base, BaseModel):
1247 class UserEmailMap(Base, BaseModel):
1230 __tablename__ = 'user_email_map'
1248 __tablename__ = 'user_email_map'
1231 __table_args__ = (
1249 __table_args__ = (
1232 Index('uem_email_idx', 'email'),
1250 Index('uem_email_idx', 'email'),
1233 UniqueConstraint('email'),
1251 UniqueConstraint('email'),
1234 base_table_args
1252 base_table_args
1235 )
1253 )
1236 __mapper_args__ = {}
1254 __mapper_args__ = {}
1237
1255
1238 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1256 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1239 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1257 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1240 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1258 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1241 user = relationship('User', lazy='joined')
1259 user = relationship('User', lazy='joined')
1242
1260
1243 @validates('_email')
1261 @validates('_email')
1244 def validate_email(self, key, email):
1262 def validate_email(self, key, email):
1245 # check if this email is not main one
1263 # check if this email is not main one
1246 main_email = Session().query(User).filter(User.email == email).scalar()
1264 main_email = Session().query(User).filter(User.email == email).scalar()
1247 if main_email is not None:
1265 if main_email is not None:
1248 raise AttributeError('email %s is present is user table' % email)
1266 raise AttributeError('email %s is present is user table' % email)
1249 return email
1267 return email
1250
1268
1251 @hybrid_property
1269 @hybrid_property
1252 def email(self):
1270 def email(self):
1253 return self._email
1271 return self._email
1254
1272
1255 @email.setter
1273 @email.setter
1256 def email(self, val):
1274 def email(self, val):
1257 self._email = val.lower() if val else None
1275 self._email = val.lower() if val else None
1258
1276
1259
1277
1260 class UserIpMap(Base, BaseModel):
1278 class UserIpMap(Base, BaseModel):
1261 __tablename__ = 'user_ip_map'
1279 __tablename__ = 'user_ip_map'
1262 __table_args__ = (
1280 __table_args__ = (
1263 UniqueConstraint('user_id', 'ip_addr'),
1281 UniqueConstraint('user_id', 'ip_addr'),
1264 base_table_args
1282 base_table_args
1265 )
1283 )
1266 __mapper_args__ = {}
1284 __mapper_args__ = {}
1267
1285
1268 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1286 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1269 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1287 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1270 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1288 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1271 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1289 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1272 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1290 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1273 user = relationship('User', lazy='joined')
1291 user = relationship('User', lazy='joined')
1274
1292
1275 @hybrid_property
1293 @hybrid_property
1276 def description_safe(self):
1294 def description_safe(self):
1277 from rhodecode.lib import helpers as h
1295 from rhodecode.lib import helpers as h
1278 return h.escape(self.description)
1296 return h.escape(self.description)
1279
1297
1280 @classmethod
1298 @classmethod
1281 def _get_ip_range(cls, ip_addr):
1299 def _get_ip_range(cls, ip_addr):
1282 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1300 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1283 return [str(net.network_address), str(net.broadcast_address)]
1301 return [str(net.network_address), str(net.broadcast_address)]
1284
1302
1285 def __json__(self):
1303 def __json__(self):
1286 return {
1304 return {
1287 'ip_addr': self.ip_addr,
1305 'ip_addr': self.ip_addr,
1288 'ip_range': self._get_ip_range(self.ip_addr),
1306 'ip_range': self._get_ip_range(self.ip_addr),
1289 }
1307 }
1290
1308
1291 def __unicode__(self):
1309 def __unicode__(self):
1292 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1310 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1293 self.user_id, self.ip_addr)
1311 self.user_id, self.ip_addr)
1294
1312
1295
1313
1296 class UserSshKeys(Base, BaseModel):
1314 class UserSshKeys(Base, BaseModel):
1297 __tablename__ = 'user_ssh_keys'
1315 __tablename__ = 'user_ssh_keys'
1298 __table_args__ = (
1316 __table_args__ = (
1299 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1317 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1300
1318
1301 UniqueConstraint('ssh_key_fingerprint'),
1319 UniqueConstraint('ssh_key_fingerprint'),
1302
1320
1303 base_table_args
1321 base_table_args
1304 )
1322 )
1305 __mapper_args__ = {}
1323 __mapper_args__ = {}
1306
1324
1307 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1325 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1308 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1326 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1309 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1327 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1310
1328
1311 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1329 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1312
1330
1313 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1331 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1314 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1332 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1315 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1333 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1316
1334
1317 user = relationship('User', lazy='joined')
1335 user = relationship('User', lazy='joined')
1318
1336
1319 def __json__(self):
1337 def __json__(self):
1320 data = {
1338 data = {
1321 'ssh_fingerprint': self.ssh_key_fingerprint,
1339 'ssh_fingerprint': self.ssh_key_fingerprint,
1322 'description': self.description,
1340 'description': self.description,
1323 'created_on': self.created_on
1341 'created_on': self.created_on
1324 }
1342 }
1325 return data
1343 return data
1326
1344
1327 def get_api_data(self):
1345 def get_api_data(self):
1328 data = self.__json__()
1346 data = self.__json__()
1329 return data
1347 return data
1330
1348
1331
1349
1332 class UserLog(Base, BaseModel):
1350 class UserLog(Base, BaseModel):
1333 __tablename__ = 'user_logs'
1351 __tablename__ = 'user_logs'
1334 __table_args__ = (
1352 __table_args__ = (
1335 base_table_args,
1353 base_table_args,
1336 )
1354 )
1337
1355
1338 VERSION_1 = 'v1'
1356 VERSION_1 = 'v1'
1339 VERSION_2 = 'v2'
1357 VERSION_2 = 'v2'
1340 VERSIONS = [VERSION_1, VERSION_2]
1358 VERSIONS = [VERSION_1, VERSION_2]
1341
1359
1342 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1360 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1343 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1361 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1344 username = Column("username", String(255), nullable=True, unique=None, default=None)
1362 username = Column("username", String(255), nullable=True, unique=None, default=None)
1345 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1363 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1346 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1364 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1347 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1365 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1348 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1366 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1349 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1367 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1350
1368
1351 version = Column("version", String(255), nullable=True, default=VERSION_1)
1369 version = Column("version", String(255), nullable=True, default=VERSION_1)
1352 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1370 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1353 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1371 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1354
1372
1355 def __unicode__(self):
1373 def __unicode__(self):
1356 return u"<%s('id:%s:%s')>" % (
1374 return u"<%s('id:%s:%s')>" % (
1357 self.__class__.__name__, self.repository_name, self.action)
1375 self.__class__.__name__, self.repository_name, self.action)
1358
1376
1359 def __json__(self):
1377 def __json__(self):
1360 return {
1378 return {
1361 'user_id': self.user_id,
1379 'user_id': self.user_id,
1362 'username': self.username,
1380 'username': self.username,
1363 'repository_id': self.repository_id,
1381 'repository_id': self.repository_id,
1364 'repository_name': self.repository_name,
1382 'repository_name': self.repository_name,
1365 'user_ip': self.user_ip,
1383 'user_ip': self.user_ip,
1366 'action_date': self.action_date,
1384 'action_date': self.action_date,
1367 'action': self.action,
1385 'action': self.action,
1368 }
1386 }
1369
1387
1370 @hybrid_property
1388 @hybrid_property
1371 def entry_id(self):
1389 def entry_id(self):
1372 return self.user_log_id
1390 return self.user_log_id
1373
1391
1374 @property
1392 @property
1375 def action_as_day(self):
1393 def action_as_day(self):
1376 return datetime.date(*self.action_date.timetuple()[:3])
1394 return datetime.date(*self.action_date.timetuple()[:3])
1377
1395
1378 user = relationship('User')
1396 user = relationship('User')
1379 repository = relationship('Repository', cascade='')
1397 repository = relationship('Repository', cascade='')
1380
1398
1381
1399
1382 class UserGroup(Base, BaseModel):
1400 class UserGroup(Base, BaseModel):
1383 __tablename__ = 'users_groups'
1401 __tablename__ = 'users_groups'
1384 __table_args__ = (
1402 __table_args__ = (
1385 base_table_args,
1403 base_table_args,
1386 )
1404 )
1387
1405
1388 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1406 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1389 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1407 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1390 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1408 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1391 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1409 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1392 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1410 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1411 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1394 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1412 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1395 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1413 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1396
1414
1397 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1415 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1398 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1416 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1399 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1417 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1400 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1418 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1401 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1419 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1402 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1420 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1403
1421
1404 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1422 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1405 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1423 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1406
1424
1407 @classmethod
1425 @classmethod
1408 def _load_group_data(cls, column):
1426 def _load_group_data(cls, column):
1409 if not column:
1427 if not column:
1410 return {}
1428 return {}
1411
1429
1412 try:
1430 try:
1413 return json.loads(column) or {}
1431 return json.loads(column) or {}
1414 except TypeError:
1432 except TypeError:
1415 return {}
1433 return {}
1416
1434
1417 @hybrid_property
1435 @hybrid_property
1418 def description_safe(self):
1436 def description_safe(self):
1419 from rhodecode.lib import helpers as h
1437 from rhodecode.lib import helpers as h
1420 return h.escape(self.user_group_description)
1438 return h.escape(self.user_group_description)
1421
1439
1422 @hybrid_property
1440 @hybrid_property
1423 def group_data(self):
1441 def group_data(self):
1424 return self._load_group_data(self._group_data)
1442 return self._load_group_data(self._group_data)
1425
1443
1426 @group_data.expression
1444 @group_data.expression
1427 def group_data(self, **kwargs):
1445 def group_data(self, **kwargs):
1428 return self._group_data
1446 return self._group_data
1429
1447
1430 @group_data.setter
1448 @group_data.setter
1431 def group_data(self, val):
1449 def group_data(self, val):
1432 try:
1450 try:
1433 self._group_data = json.dumps(val)
1451 self._group_data = json.dumps(val)
1434 except Exception:
1452 except Exception:
1435 log.error(traceback.format_exc())
1453 log.error(traceback.format_exc())
1436
1454
1437 @classmethod
1455 @classmethod
1438 def _load_sync(cls, group_data):
1456 def _load_sync(cls, group_data):
1439 if group_data:
1457 if group_data:
1440 return group_data.get('extern_type')
1458 return group_data.get('extern_type')
1441
1459
1442 @property
1460 @property
1443 def sync(self):
1461 def sync(self):
1444 return self._load_sync(self.group_data)
1462 return self._load_sync(self.group_data)
1445
1463
1446 def __unicode__(self):
1464 def __unicode__(self):
1447 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1465 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1448 self.users_group_id,
1466 self.users_group_id,
1449 self.users_group_name)
1467 self.users_group_name)
1450
1468
1451 @classmethod
1469 @classmethod
1452 def get_by_group_name(cls, group_name, cache=False,
1470 def get_by_group_name(cls, group_name, cache=False,
1453 case_insensitive=False):
1471 case_insensitive=False):
1454 if case_insensitive:
1472 if case_insensitive:
1455 q = cls.query().filter(func.lower(cls.users_group_name) ==
1473 q = cls.query().filter(func.lower(cls.users_group_name) ==
1456 func.lower(group_name))
1474 func.lower(group_name))
1457
1475
1458 else:
1476 else:
1459 q = cls.query().filter(cls.users_group_name == group_name)
1477 q = cls.query().filter(cls.users_group_name == group_name)
1460 if cache:
1478 if cache:
1461 q = q.options(
1479 q = q.options(
1462 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1480 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1463 return q.scalar()
1481 return q.scalar()
1464
1482
1465 @classmethod
1483 @classmethod
1466 def get(cls, user_group_id, cache=False):
1484 def get(cls, user_group_id, cache=False):
1467 if not user_group_id:
1485 if not user_group_id:
1468 return
1486 return
1469
1487
1470 user_group = cls.query()
1488 user_group = cls.query()
1471 if cache:
1489 if cache:
1472 user_group = user_group.options(
1490 user_group = user_group.options(
1473 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1491 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1474 return user_group.get(user_group_id)
1492 return user_group.get(user_group_id)
1475
1493
1476 def permissions(self, with_admins=True, with_owner=True,
1494 def permissions(self, with_admins=True, with_owner=True,
1477 expand_from_user_groups=False):
1495 expand_from_user_groups=False):
1478 """
1496 """
1479 Permissions for user groups
1497 Permissions for user groups
1480 """
1498 """
1481 _admin_perm = 'usergroup.admin'
1499 _admin_perm = 'usergroup.admin'
1482
1500
1483 owner_row = []
1501 owner_row = []
1484 if with_owner:
1502 if with_owner:
1485 usr = AttributeDict(self.user.get_dict())
1503 usr = AttributeDict(self.user.get_dict())
1486 usr.owner_row = True
1504 usr.owner_row = True
1487 usr.permission = _admin_perm
1505 usr.permission = _admin_perm
1488 owner_row.append(usr)
1506 owner_row.append(usr)
1489
1507
1490 super_admin_ids = []
1508 super_admin_ids = []
1491 super_admin_rows = []
1509 super_admin_rows = []
1492 if with_admins:
1510 if with_admins:
1493 for usr in User.get_all_super_admins():
1511 for usr in User.get_all_super_admins():
1494 super_admin_ids.append(usr.user_id)
1512 super_admin_ids.append(usr.user_id)
1495 # if this admin is also owner, don't double the record
1513 # if this admin is also owner, don't double the record
1496 if usr.user_id == owner_row[0].user_id:
1514 if usr.user_id == owner_row[0].user_id:
1497 owner_row[0].admin_row = True
1515 owner_row[0].admin_row = True
1498 else:
1516 else:
1499 usr = AttributeDict(usr.get_dict())
1517 usr = AttributeDict(usr.get_dict())
1500 usr.admin_row = True
1518 usr.admin_row = True
1501 usr.permission = _admin_perm
1519 usr.permission = _admin_perm
1502 super_admin_rows.append(usr)
1520 super_admin_rows.append(usr)
1503
1521
1504 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1522 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1505 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1523 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1506 joinedload(UserUserGroupToPerm.user),
1524 joinedload(UserUserGroupToPerm.user),
1507 joinedload(UserUserGroupToPerm.permission),)
1525 joinedload(UserUserGroupToPerm.permission),)
1508
1526
1509 # get owners and admins and permissions. We do a trick of re-writing
1527 # get owners and admins and permissions. We do a trick of re-writing
1510 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1528 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1511 # has a global reference and changing one object propagates to all
1529 # has a global reference and changing one object propagates to all
1512 # others. This means if admin is also an owner admin_row that change
1530 # others. This means if admin is also an owner admin_row that change
1513 # would propagate to both objects
1531 # would propagate to both objects
1514 perm_rows = []
1532 perm_rows = []
1515 for _usr in q.all():
1533 for _usr in q.all():
1516 usr = AttributeDict(_usr.user.get_dict())
1534 usr = AttributeDict(_usr.user.get_dict())
1517 # if this user is also owner/admin, mark as duplicate record
1535 # if this user is also owner/admin, mark as duplicate record
1518 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1536 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1519 usr.duplicate_perm = True
1537 usr.duplicate_perm = True
1520 usr.permission = _usr.permission.permission_name
1538 usr.permission = _usr.permission.permission_name
1521 perm_rows.append(usr)
1539 perm_rows.append(usr)
1522
1540
1523 # filter the perm rows by 'default' first and then sort them by
1541 # filter the perm rows by 'default' first and then sort them by
1524 # admin,write,read,none permissions sorted again alphabetically in
1542 # admin,write,read,none permissions sorted again alphabetically in
1525 # each group
1543 # each group
1526 perm_rows = sorted(perm_rows, key=display_user_sort)
1544 perm_rows = sorted(perm_rows, key=display_user_sort)
1527
1545
1528 user_groups_rows = []
1546 user_groups_rows = []
1529 if expand_from_user_groups:
1547 if expand_from_user_groups:
1530 for ug in self.permission_user_groups(with_members=True):
1548 for ug in self.permission_user_groups(with_members=True):
1531 for user_data in ug.members:
1549 for user_data in ug.members:
1532 user_groups_rows.append(user_data)
1550 user_groups_rows.append(user_data)
1533
1551
1534 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1552 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1535
1553
1536 def permission_user_groups(self, with_members=False):
1554 def permission_user_groups(self, with_members=False):
1537 q = UserGroupUserGroupToPerm.query()\
1555 q = UserGroupUserGroupToPerm.query()\
1538 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1556 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1539 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1557 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1540 joinedload(UserGroupUserGroupToPerm.target_user_group),
1558 joinedload(UserGroupUserGroupToPerm.target_user_group),
1541 joinedload(UserGroupUserGroupToPerm.permission),)
1559 joinedload(UserGroupUserGroupToPerm.permission),)
1542
1560
1543 perm_rows = []
1561 perm_rows = []
1544 for _user_group in q.all():
1562 for _user_group in q.all():
1545 entry = AttributeDict(_user_group.user_group.get_dict())
1563 entry = AttributeDict(_user_group.user_group.get_dict())
1546 entry.permission = _user_group.permission.permission_name
1564 entry.permission = _user_group.permission.permission_name
1547 if with_members:
1565 if with_members:
1548 entry.members = [x.user.get_dict()
1566 entry.members = [x.user.get_dict()
1549 for x in _user_group.user_group.members]
1567 for x in _user_group.user_group.members]
1550 perm_rows.append(entry)
1568 perm_rows.append(entry)
1551
1569
1552 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1570 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1553 return perm_rows
1571 return perm_rows
1554
1572
1555 def _get_default_perms(self, user_group, suffix=''):
1573 def _get_default_perms(self, user_group, suffix=''):
1556 from rhodecode.model.permission import PermissionModel
1574 from rhodecode.model.permission import PermissionModel
1557 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1575 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1558
1576
1559 def get_default_perms(self, suffix=''):
1577 def get_default_perms(self, suffix=''):
1560 return self._get_default_perms(self, suffix)
1578 return self._get_default_perms(self, suffix)
1561
1579
1562 def get_api_data(self, with_group_members=True, include_secrets=False):
1580 def get_api_data(self, with_group_members=True, include_secrets=False):
1563 """
1581 """
1564 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1582 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1565 basically forwarded.
1583 basically forwarded.
1566
1584
1567 """
1585 """
1568 user_group = self
1586 user_group = self
1569 data = {
1587 data = {
1570 'users_group_id': user_group.users_group_id,
1588 'users_group_id': user_group.users_group_id,
1571 'group_name': user_group.users_group_name,
1589 'group_name': user_group.users_group_name,
1572 'group_description': user_group.user_group_description,
1590 'group_description': user_group.user_group_description,
1573 'active': user_group.users_group_active,
1591 'active': user_group.users_group_active,
1574 'owner': user_group.user.username,
1592 'owner': user_group.user.username,
1575 'sync': user_group.sync,
1593 'sync': user_group.sync,
1576 'owner_email': user_group.user.email,
1594 'owner_email': user_group.user.email,
1577 }
1595 }
1578
1596
1579 if with_group_members:
1597 if with_group_members:
1580 users = []
1598 users = []
1581 for user in user_group.members:
1599 for user in user_group.members:
1582 user = user.user
1600 user = user.user
1583 users.append(user.get_api_data(include_secrets=include_secrets))
1601 users.append(user.get_api_data(include_secrets=include_secrets))
1584 data['users'] = users
1602 data['users'] = users
1585
1603
1586 return data
1604 return data
1587
1605
1588
1606
1589 class UserGroupMember(Base, BaseModel):
1607 class UserGroupMember(Base, BaseModel):
1590 __tablename__ = 'users_groups_members'
1608 __tablename__ = 'users_groups_members'
1591 __table_args__ = (
1609 __table_args__ = (
1592 base_table_args,
1610 base_table_args,
1593 )
1611 )
1594
1612
1595 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1613 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1596 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1614 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1615 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1598
1616
1599 user = relationship('User', lazy='joined')
1617 user = relationship('User', lazy='joined')
1600 users_group = relationship('UserGroup')
1618 users_group = relationship('UserGroup')
1601
1619
1602 def __init__(self, gr_id='', u_id=''):
1620 def __init__(self, gr_id='', u_id=''):
1603 self.users_group_id = gr_id
1621 self.users_group_id = gr_id
1604 self.user_id = u_id
1622 self.user_id = u_id
1605
1623
1606
1624
1607 class RepositoryField(Base, BaseModel):
1625 class RepositoryField(Base, BaseModel):
1608 __tablename__ = 'repositories_fields'
1626 __tablename__ = 'repositories_fields'
1609 __table_args__ = (
1627 __table_args__ = (
1610 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1628 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1611 base_table_args,
1629 base_table_args,
1612 )
1630 )
1613
1631
1614 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1632 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1615
1633
1616 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1634 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1617 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1635 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1618 field_key = Column("field_key", String(250))
1636 field_key = Column("field_key", String(250))
1619 field_label = Column("field_label", String(1024), nullable=False)
1637 field_label = Column("field_label", String(1024), nullable=False)
1620 field_value = Column("field_value", String(10000), nullable=False)
1638 field_value = Column("field_value", String(10000), nullable=False)
1621 field_desc = Column("field_desc", String(1024), nullable=False)
1639 field_desc = Column("field_desc", String(1024), nullable=False)
1622 field_type = Column("field_type", String(255), nullable=False, unique=None)
1640 field_type = Column("field_type", String(255), nullable=False, unique=None)
1623 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1641 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1624
1642
1625 repository = relationship('Repository')
1643 repository = relationship('Repository')
1626
1644
1627 @property
1645 @property
1628 def field_key_prefixed(self):
1646 def field_key_prefixed(self):
1629 return 'ex_%s' % self.field_key
1647 return 'ex_%s' % self.field_key
1630
1648
1631 @classmethod
1649 @classmethod
1632 def un_prefix_key(cls, key):
1650 def un_prefix_key(cls, key):
1633 if key.startswith(cls.PREFIX):
1651 if key.startswith(cls.PREFIX):
1634 return key[len(cls.PREFIX):]
1652 return key[len(cls.PREFIX):]
1635 return key
1653 return key
1636
1654
1637 @classmethod
1655 @classmethod
1638 def get_by_key_name(cls, key, repo):
1656 def get_by_key_name(cls, key, repo):
1639 row = cls.query()\
1657 row = cls.query()\
1640 .filter(cls.repository == repo)\
1658 .filter(cls.repository == repo)\
1641 .filter(cls.field_key == key).scalar()
1659 .filter(cls.field_key == key).scalar()
1642 return row
1660 return row
1643
1661
1644
1662
1645 class Repository(Base, BaseModel):
1663 class Repository(Base, BaseModel):
1646 __tablename__ = 'repositories'
1664 __tablename__ = 'repositories'
1647 __table_args__ = (
1665 __table_args__ = (
1648 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1666 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1649 base_table_args,
1667 base_table_args,
1650 )
1668 )
1651 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1669 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1652 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1670 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1653 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1671 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1654
1672
1655 STATE_CREATED = 'repo_state_created'
1673 STATE_CREATED = 'repo_state_created'
1656 STATE_PENDING = 'repo_state_pending'
1674 STATE_PENDING = 'repo_state_pending'
1657 STATE_ERROR = 'repo_state_error'
1675 STATE_ERROR = 'repo_state_error'
1658
1676
1659 LOCK_AUTOMATIC = 'lock_auto'
1677 LOCK_AUTOMATIC = 'lock_auto'
1660 LOCK_API = 'lock_api'
1678 LOCK_API = 'lock_api'
1661 LOCK_WEB = 'lock_web'
1679 LOCK_WEB = 'lock_web'
1662 LOCK_PULL = 'lock_pull'
1680 LOCK_PULL = 'lock_pull'
1663
1681
1664 NAME_SEP = URL_SEP
1682 NAME_SEP = URL_SEP
1665
1683
1666 repo_id = Column(
1684 repo_id = Column(
1667 "repo_id", Integer(), nullable=False, unique=True, default=None,
1685 "repo_id", Integer(), nullable=False, unique=True, default=None,
1668 primary_key=True)
1686 primary_key=True)
1669 _repo_name = Column(
1687 _repo_name = Column(
1670 "repo_name", Text(), nullable=False, default=None)
1688 "repo_name", Text(), nullable=False, default=None)
1671 repo_name_hash = Column(
1689 repo_name_hash = Column(
1672 "repo_name_hash", String(255), nullable=False, unique=True)
1690 "repo_name_hash", String(255), nullable=False, unique=True)
1673 repo_state = Column("repo_state", String(255), nullable=True)
1691 repo_state = Column("repo_state", String(255), nullable=True)
1674
1692
1675 clone_uri = Column(
1693 clone_uri = Column(
1676 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1694 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1677 default=None)
1695 default=None)
1678 push_uri = Column(
1696 push_uri = Column(
1679 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1697 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1680 default=None)
1698 default=None)
1681 repo_type = Column(
1699 repo_type = Column(
1682 "repo_type", String(255), nullable=False, unique=False, default=None)
1700 "repo_type", String(255), nullable=False, unique=False, default=None)
1683 user_id = Column(
1701 user_id = Column(
1684 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1702 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1685 unique=False, default=None)
1703 unique=False, default=None)
1686 private = Column(
1704 private = Column(
1687 "private", Boolean(), nullable=True, unique=None, default=None)
1705 "private", Boolean(), nullable=True, unique=None, default=None)
1688 archived = Column(
1706 archived = Column(
1689 "archived", Boolean(), nullable=True, unique=None, default=None)
1707 "archived", Boolean(), nullable=True, unique=None, default=None)
1690 enable_statistics = Column(
1708 enable_statistics = Column(
1691 "statistics", Boolean(), nullable=True, unique=None, default=True)
1709 "statistics", Boolean(), nullable=True, unique=None, default=True)
1692 enable_downloads = Column(
1710 enable_downloads = Column(
1693 "downloads", Boolean(), nullable=True, unique=None, default=True)
1711 "downloads", Boolean(), nullable=True, unique=None, default=True)
1694 description = Column(
1712 description = Column(
1695 "description", String(10000), nullable=True, unique=None, default=None)
1713 "description", String(10000), nullable=True, unique=None, default=None)
1696 created_on = Column(
1714 created_on = Column(
1697 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1715 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1698 default=datetime.datetime.now)
1716 default=datetime.datetime.now)
1699 updated_on = Column(
1717 updated_on = Column(
1700 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1718 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1701 default=datetime.datetime.now)
1719 default=datetime.datetime.now)
1702 _landing_revision = Column(
1720 _landing_revision = Column(
1703 "landing_revision", String(255), nullable=False, unique=False,
1721 "landing_revision", String(255), nullable=False, unique=False,
1704 default=None)
1722 default=None)
1705 enable_locking = Column(
1723 enable_locking = Column(
1706 "enable_locking", Boolean(), nullable=False, unique=None,
1724 "enable_locking", Boolean(), nullable=False, unique=None,
1707 default=False)
1725 default=False)
1708 _locked = Column(
1726 _locked = Column(
1709 "locked", String(255), nullable=True, unique=False, default=None)
1727 "locked", String(255), nullable=True, unique=False, default=None)
1710 _changeset_cache = Column(
1728 _changeset_cache = Column(
1711 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1729 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1712
1730
1713 fork_id = Column(
1731 fork_id = Column(
1714 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1732 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1715 nullable=True, unique=False, default=None)
1733 nullable=True, unique=False, default=None)
1716 group_id = Column(
1734 group_id = Column(
1717 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1735 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1718 unique=False, default=None)
1736 unique=False, default=None)
1719
1737
1720 user = relationship('User', lazy='joined')
1738 user = relationship('User', lazy='joined')
1721 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1739 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1722 group = relationship('RepoGroup', lazy='joined')
1740 group = relationship('RepoGroup', lazy='joined')
1723 repo_to_perm = relationship(
1741 repo_to_perm = relationship(
1724 'UserRepoToPerm', cascade='all',
1742 'UserRepoToPerm', cascade='all',
1725 order_by='UserRepoToPerm.repo_to_perm_id')
1743 order_by='UserRepoToPerm.repo_to_perm_id')
1726 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1744 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1727 stats = relationship('Statistics', cascade='all', uselist=False)
1745 stats = relationship('Statistics', cascade='all', uselist=False)
1728
1746
1729 followers = relationship(
1747 followers = relationship(
1730 'UserFollowing',
1748 'UserFollowing',
1731 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1749 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1732 cascade='all')
1750 cascade='all')
1733 extra_fields = relationship(
1751 extra_fields = relationship(
1734 'RepositoryField', cascade="all, delete-orphan")
1752 'RepositoryField', cascade="all, delete-orphan")
1735 logs = relationship('UserLog')
1753 logs = relationship('UserLog')
1736 comments = relationship(
1754 comments = relationship(
1737 'ChangesetComment', cascade="all, delete-orphan")
1755 'ChangesetComment', cascade="all, delete-orphan")
1738 pull_requests_source = relationship(
1756 pull_requests_source = relationship(
1739 'PullRequest',
1757 'PullRequest',
1740 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1758 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1741 cascade="all, delete-orphan")
1759 cascade="all, delete-orphan")
1742 pull_requests_target = relationship(
1760 pull_requests_target = relationship(
1743 'PullRequest',
1761 'PullRequest',
1744 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1762 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1745 cascade="all, delete-orphan")
1763 cascade="all, delete-orphan")
1746 ui = relationship('RepoRhodeCodeUi', cascade="all")
1764 ui = relationship('RepoRhodeCodeUi', cascade="all")
1747 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1765 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1748 integrations = relationship('Integration', cascade="all, delete-orphan")
1766 integrations = relationship('Integration', cascade="all, delete-orphan")
1749
1767
1750 scoped_tokens = relationship('UserApiKeys', cascade="all")
1768 scoped_tokens = relationship('UserApiKeys', cascade="all")
1751
1769
1752 # no cascade, set NULL
1770 # no cascade, set NULL
1753 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1771 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1754
1772
1755 def __unicode__(self):
1773 def __unicode__(self):
1756 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1774 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1757 safe_unicode(self.repo_name))
1775 safe_unicode(self.repo_name))
1758
1776
1759 @hybrid_property
1777 @hybrid_property
1760 def description_safe(self):
1778 def description_safe(self):
1761 from rhodecode.lib import helpers as h
1779 from rhodecode.lib import helpers as h
1762 return h.escape(self.description)
1780 return h.escape(self.description)
1763
1781
1764 @hybrid_property
1782 @hybrid_property
1765 def landing_rev(self):
1783 def landing_rev(self):
1766 # always should return [rev_type, rev], e.g ['branch', 'master']
1784 # always should return [rev_type, rev], e.g ['branch', 'master']
1767 if self._landing_revision:
1785 if self._landing_revision:
1768 _rev_info = self._landing_revision.split(':')
1786 _rev_info = self._landing_revision.split(':')
1769 if len(_rev_info) < 2:
1787 if len(_rev_info) < 2:
1770 _rev_info.insert(0, 'rev')
1788 _rev_info.insert(0, 'rev')
1771 return [_rev_info[0], _rev_info[1]]
1789 return [_rev_info[0], _rev_info[1]]
1772 return [None, None]
1790 return [None, None]
1773
1791
1774 @property
1792 @property
1775 def landing_ref_type(self):
1793 def landing_ref_type(self):
1776 return self.landing_rev[0]
1794 return self.landing_rev[0]
1777
1795
1778 @property
1796 @property
1779 def landing_ref_name(self):
1797 def landing_ref_name(self):
1780 return self.landing_rev[1]
1798 return self.landing_rev[1]
1781
1799
1782 @landing_rev.setter
1800 @landing_rev.setter
1783 def landing_rev(self, val):
1801 def landing_rev(self, val):
1784 if ':' not in val:
1802 if ':' not in val:
1785 raise ValueError('value must be delimited with `:` and consist '
1803 raise ValueError('value must be delimited with `:` and consist '
1786 'of <rev_type>:<rev>, got %s instead' % val)
1804 'of <rev_type>:<rev>, got %s instead' % val)
1787 self._landing_revision = val
1805 self._landing_revision = val
1788
1806
1789 @hybrid_property
1807 @hybrid_property
1790 def locked(self):
1808 def locked(self):
1791 if self._locked:
1809 if self._locked:
1792 user_id, timelocked, reason = self._locked.split(':')
1810 user_id, timelocked, reason = self._locked.split(':')
1793 lock_values = int(user_id), timelocked, reason
1811 lock_values = int(user_id), timelocked, reason
1794 else:
1812 else:
1795 lock_values = [None, None, None]
1813 lock_values = [None, None, None]
1796 return lock_values
1814 return lock_values
1797
1815
1798 @locked.setter
1816 @locked.setter
1799 def locked(self, val):
1817 def locked(self, val):
1800 if val and isinstance(val, (list, tuple)):
1818 if val and isinstance(val, (list, tuple)):
1801 self._locked = ':'.join(map(str, val))
1819 self._locked = ':'.join(map(str, val))
1802 else:
1820 else:
1803 self._locked = None
1821 self._locked = None
1804
1822
1805 @classmethod
1823 @classmethod
1806 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1824 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1807 from rhodecode.lib.vcs.backends.base import EmptyCommit
1825 from rhodecode.lib.vcs.backends.base import EmptyCommit
1808 dummy = EmptyCommit().__json__()
1826 dummy = EmptyCommit().__json__()
1809 if not changeset_cache_raw:
1827 if not changeset_cache_raw:
1810 dummy['source_repo_id'] = repo_id
1828 dummy['source_repo_id'] = repo_id
1811 return json.loads(json.dumps(dummy))
1829 return json.loads(json.dumps(dummy))
1812
1830
1813 try:
1831 try:
1814 return json.loads(changeset_cache_raw)
1832 return json.loads(changeset_cache_raw)
1815 except TypeError:
1833 except TypeError:
1816 return dummy
1834 return dummy
1817 except Exception:
1835 except Exception:
1818 log.error(traceback.format_exc())
1836 log.error(traceback.format_exc())
1819 return dummy
1837 return dummy
1820
1838
1821 @hybrid_property
1839 @hybrid_property
1822 def changeset_cache(self):
1840 def changeset_cache(self):
1823 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1841 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1824
1842
1825 @changeset_cache.setter
1843 @changeset_cache.setter
1826 def changeset_cache(self, val):
1844 def changeset_cache(self, val):
1827 try:
1845 try:
1828 self._changeset_cache = json.dumps(val)
1846 self._changeset_cache = json.dumps(val)
1829 except Exception:
1847 except Exception:
1830 log.error(traceback.format_exc())
1848 log.error(traceback.format_exc())
1831
1849
1832 @hybrid_property
1850 @hybrid_property
1833 def repo_name(self):
1851 def repo_name(self):
1834 return self._repo_name
1852 return self._repo_name
1835
1853
1836 @repo_name.setter
1854 @repo_name.setter
1837 def repo_name(self, value):
1855 def repo_name(self, value):
1838 self._repo_name = value
1856 self._repo_name = value
1839 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1857 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1840
1858
1841 @classmethod
1859 @classmethod
1842 def normalize_repo_name(cls, repo_name):
1860 def normalize_repo_name(cls, repo_name):
1843 """
1861 """
1844 Normalizes os specific repo_name to the format internally stored inside
1862 Normalizes os specific repo_name to the format internally stored inside
1845 database using URL_SEP
1863 database using URL_SEP
1846
1864
1847 :param cls:
1865 :param cls:
1848 :param repo_name:
1866 :param repo_name:
1849 """
1867 """
1850 return cls.NAME_SEP.join(repo_name.split(os.sep))
1868 return cls.NAME_SEP.join(repo_name.split(os.sep))
1851
1869
1852 @classmethod
1870 @classmethod
1853 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1871 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1854 session = Session()
1872 session = Session()
1855 q = session.query(cls).filter(cls.repo_name == repo_name)
1873 q = session.query(cls).filter(cls.repo_name == repo_name)
1856
1874
1857 if cache:
1875 if cache:
1858 if identity_cache:
1876 if identity_cache:
1859 val = cls.identity_cache(session, 'repo_name', repo_name)
1877 val = cls.identity_cache(session, 'repo_name', repo_name)
1860 if val:
1878 if val:
1861 return val
1879 return val
1862 else:
1880 else:
1863 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1881 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1864 q = q.options(
1882 q = q.options(
1865 FromCache("sql_cache_short", cache_key))
1883 FromCache("sql_cache_short", cache_key))
1866
1884
1867 return q.scalar()
1885 return q.scalar()
1868
1886
1869 @classmethod
1887 @classmethod
1870 def get_by_id_or_repo_name(cls, repoid):
1888 def get_by_id_or_repo_name(cls, repoid):
1871 if isinstance(repoid, (int, long)):
1889 if isinstance(repoid, (int, long)):
1872 try:
1890 try:
1873 repo = cls.get(repoid)
1891 repo = cls.get(repoid)
1874 except ValueError:
1892 except ValueError:
1875 repo = None
1893 repo = None
1876 else:
1894 else:
1877 repo = cls.get_by_repo_name(repoid)
1895 repo = cls.get_by_repo_name(repoid)
1878 return repo
1896 return repo
1879
1897
1880 @classmethod
1898 @classmethod
1881 def get_by_full_path(cls, repo_full_path):
1899 def get_by_full_path(cls, repo_full_path):
1882 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1900 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1883 repo_name = cls.normalize_repo_name(repo_name)
1901 repo_name = cls.normalize_repo_name(repo_name)
1884 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1902 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1885
1903
1886 @classmethod
1904 @classmethod
1887 def get_repo_forks(cls, repo_id):
1905 def get_repo_forks(cls, repo_id):
1888 return cls.query().filter(Repository.fork_id == repo_id)
1906 return cls.query().filter(Repository.fork_id == repo_id)
1889
1907
1890 @classmethod
1908 @classmethod
1891 def base_path(cls):
1909 def base_path(cls):
1892 """
1910 """
1893 Returns base path when all repos are stored
1911 Returns base path when all repos are stored
1894
1912
1895 :param cls:
1913 :param cls:
1896 """
1914 """
1897 q = Session().query(RhodeCodeUi)\
1915 q = Session().query(RhodeCodeUi)\
1898 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1916 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1899 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1917 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1900 return q.one().ui_value
1918 return q.one().ui_value
1901
1919
1902 @classmethod
1920 @classmethod
1903 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1921 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1904 case_insensitive=True, archived=False):
1922 case_insensitive=True, archived=False):
1905 q = Repository.query()
1923 q = Repository.query()
1906
1924
1907 if not archived:
1925 if not archived:
1908 q = q.filter(Repository.archived.isnot(true()))
1926 q = q.filter(Repository.archived.isnot(true()))
1909
1927
1910 if not isinstance(user_id, Optional):
1928 if not isinstance(user_id, Optional):
1911 q = q.filter(Repository.user_id == user_id)
1929 q = q.filter(Repository.user_id == user_id)
1912
1930
1913 if not isinstance(group_id, Optional):
1931 if not isinstance(group_id, Optional):
1914 q = q.filter(Repository.group_id == group_id)
1932 q = q.filter(Repository.group_id == group_id)
1915
1933
1916 if case_insensitive:
1934 if case_insensitive:
1917 q = q.order_by(func.lower(Repository.repo_name))
1935 q = q.order_by(func.lower(Repository.repo_name))
1918 else:
1936 else:
1919 q = q.order_by(Repository.repo_name)
1937 q = q.order_by(Repository.repo_name)
1920
1938
1921 return q.all()
1939 return q.all()
1922
1940
1923 @property
1941 @property
1924 def repo_uid(self):
1942 def repo_uid(self):
1925 return '_{}'.format(self.repo_id)
1943 return '_{}'.format(self.repo_id)
1926
1944
1927 @property
1945 @property
1928 def forks(self):
1946 def forks(self):
1929 """
1947 """
1930 Return forks of this repo
1948 Return forks of this repo
1931 """
1949 """
1932 return Repository.get_repo_forks(self.repo_id)
1950 return Repository.get_repo_forks(self.repo_id)
1933
1951
1934 @property
1952 @property
1935 def parent(self):
1953 def parent(self):
1936 """
1954 """
1937 Returns fork parent
1955 Returns fork parent
1938 """
1956 """
1939 return self.fork
1957 return self.fork
1940
1958
1941 @property
1959 @property
1942 def just_name(self):
1960 def just_name(self):
1943 return self.repo_name.split(self.NAME_SEP)[-1]
1961 return self.repo_name.split(self.NAME_SEP)[-1]
1944
1962
1945 @property
1963 @property
1946 def groups_with_parents(self):
1964 def groups_with_parents(self):
1947 groups = []
1965 groups = []
1948 if self.group is None:
1966 if self.group is None:
1949 return groups
1967 return groups
1950
1968
1951 cur_gr = self.group
1969 cur_gr = self.group
1952 groups.insert(0, cur_gr)
1970 groups.insert(0, cur_gr)
1953 while 1:
1971 while 1:
1954 gr = getattr(cur_gr, 'parent_group', None)
1972 gr = getattr(cur_gr, 'parent_group', None)
1955 cur_gr = cur_gr.parent_group
1973 cur_gr = cur_gr.parent_group
1956 if gr is None:
1974 if gr is None:
1957 break
1975 break
1958 groups.insert(0, gr)
1976 groups.insert(0, gr)
1959
1977
1960 return groups
1978 return groups
1961
1979
1962 @property
1980 @property
1963 def groups_and_repo(self):
1981 def groups_and_repo(self):
1964 return self.groups_with_parents, self
1982 return self.groups_with_parents, self
1965
1983
1966 @LazyProperty
1984 @LazyProperty
1967 def repo_path(self):
1985 def repo_path(self):
1968 """
1986 """
1969 Returns base full path for that repository means where it actually
1987 Returns base full path for that repository means where it actually
1970 exists on a filesystem
1988 exists on a filesystem
1971 """
1989 """
1972 q = Session().query(RhodeCodeUi).filter(
1990 q = Session().query(RhodeCodeUi).filter(
1973 RhodeCodeUi.ui_key == self.NAME_SEP)
1991 RhodeCodeUi.ui_key == self.NAME_SEP)
1974 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1992 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1975 return q.one().ui_value
1993 return q.one().ui_value
1976
1994
1977 @property
1995 @property
1978 def repo_full_path(self):
1996 def repo_full_path(self):
1979 p = [self.repo_path]
1997 p = [self.repo_path]
1980 # we need to split the name by / since this is how we store the
1998 # we need to split the name by / since this is how we store the
1981 # names in the database, but that eventually needs to be converted
1999 # names in the database, but that eventually needs to be converted
1982 # into a valid system path
2000 # into a valid system path
1983 p += self.repo_name.split(self.NAME_SEP)
2001 p += self.repo_name.split(self.NAME_SEP)
1984 return os.path.join(*map(safe_unicode, p))
2002 return os.path.join(*map(safe_unicode, p))
1985
2003
1986 @property
2004 @property
1987 def cache_keys(self):
2005 def cache_keys(self):
1988 """
2006 """
1989 Returns associated cache keys for that repo
2007 Returns associated cache keys for that repo
1990 """
2008 """
1991 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2009 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1992 repo_id=self.repo_id)
2010 repo_id=self.repo_id)
1993 return CacheKey.query()\
2011 return CacheKey.query()\
1994 .filter(CacheKey.cache_args == invalidation_namespace)\
2012 .filter(CacheKey.cache_args == invalidation_namespace)\
1995 .order_by(CacheKey.cache_key)\
2013 .order_by(CacheKey.cache_key)\
1996 .all()
2014 .all()
1997
2015
1998 @property
2016 @property
1999 def cached_diffs_relative_dir(self):
2017 def cached_diffs_relative_dir(self):
2000 """
2018 """
2001 Return a relative to the repository store path of cached diffs
2019 Return a relative to the repository store path of cached diffs
2002 used for safe display for users, who shouldn't know the absolute store
2020 used for safe display for users, who shouldn't know the absolute store
2003 path
2021 path
2004 """
2022 """
2005 return os.path.join(
2023 return os.path.join(
2006 os.path.dirname(self.repo_name),
2024 os.path.dirname(self.repo_name),
2007 self.cached_diffs_dir.split(os.path.sep)[-1])
2025 self.cached_diffs_dir.split(os.path.sep)[-1])
2008
2026
2009 @property
2027 @property
2010 def cached_diffs_dir(self):
2028 def cached_diffs_dir(self):
2011 path = self.repo_full_path
2029 path = self.repo_full_path
2012 return os.path.join(
2030 return os.path.join(
2013 os.path.dirname(path),
2031 os.path.dirname(path),
2014 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2032 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2015
2033
2016 def cached_diffs(self):
2034 def cached_diffs(self):
2017 diff_cache_dir = self.cached_diffs_dir
2035 diff_cache_dir = self.cached_diffs_dir
2018 if os.path.isdir(diff_cache_dir):
2036 if os.path.isdir(diff_cache_dir):
2019 return os.listdir(diff_cache_dir)
2037 return os.listdir(diff_cache_dir)
2020 return []
2038 return []
2021
2039
2022 def shadow_repos(self):
2040 def shadow_repos(self):
2023 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2041 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2024 return [
2042 return [
2025 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2043 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2026 if x.startswith(shadow_repos_pattern)]
2044 if x.startswith(shadow_repos_pattern)]
2027
2045
2028 def get_new_name(self, repo_name):
2046 def get_new_name(self, repo_name):
2029 """
2047 """
2030 returns new full repository name based on assigned group and new new
2048 returns new full repository name based on assigned group and new new
2031
2049
2032 :param group_name:
2050 :param group_name:
2033 """
2051 """
2034 path_prefix = self.group.full_path_splitted if self.group else []
2052 path_prefix = self.group.full_path_splitted if self.group else []
2035 return self.NAME_SEP.join(path_prefix + [repo_name])
2053 return self.NAME_SEP.join(path_prefix + [repo_name])
2036
2054
2037 @property
2055 @property
2038 def _config(self):
2056 def _config(self):
2039 """
2057 """
2040 Returns db based config object.
2058 Returns db based config object.
2041 """
2059 """
2042 from rhodecode.lib.utils import make_db_config
2060 from rhodecode.lib.utils import make_db_config
2043 return make_db_config(clear_session=False, repo=self)
2061 return make_db_config(clear_session=False, repo=self)
2044
2062
2045 def permissions(self, with_admins=True, with_owner=True,
2063 def permissions(self, with_admins=True, with_owner=True,
2046 expand_from_user_groups=False):
2064 expand_from_user_groups=False):
2047 """
2065 """
2048 Permissions for repositories
2066 Permissions for repositories
2049 """
2067 """
2050 _admin_perm = 'repository.admin'
2068 _admin_perm = 'repository.admin'
2051
2069
2052 owner_row = []
2070 owner_row = []
2053 if with_owner:
2071 if with_owner:
2054 usr = AttributeDict(self.user.get_dict())
2072 usr = AttributeDict(self.user.get_dict())
2055 usr.owner_row = True
2073 usr.owner_row = True
2056 usr.permission = _admin_perm
2074 usr.permission = _admin_perm
2057 usr.permission_id = None
2075 usr.permission_id = None
2058 owner_row.append(usr)
2076 owner_row.append(usr)
2059
2077
2060 super_admin_ids = []
2078 super_admin_ids = []
2061 super_admin_rows = []
2079 super_admin_rows = []
2062 if with_admins:
2080 if with_admins:
2063 for usr in User.get_all_super_admins():
2081 for usr in User.get_all_super_admins():
2064 super_admin_ids.append(usr.user_id)
2082 super_admin_ids.append(usr.user_id)
2065 # if this admin is also owner, don't double the record
2083 # if this admin is also owner, don't double the record
2066 if usr.user_id == owner_row[0].user_id:
2084 if usr.user_id == owner_row[0].user_id:
2067 owner_row[0].admin_row = True
2085 owner_row[0].admin_row = True
2068 else:
2086 else:
2069 usr = AttributeDict(usr.get_dict())
2087 usr = AttributeDict(usr.get_dict())
2070 usr.admin_row = True
2088 usr.admin_row = True
2071 usr.permission = _admin_perm
2089 usr.permission = _admin_perm
2072 usr.permission_id = None
2090 usr.permission_id = None
2073 super_admin_rows.append(usr)
2091 super_admin_rows.append(usr)
2074
2092
2075 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2093 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2076 q = q.options(joinedload(UserRepoToPerm.repository),
2094 q = q.options(joinedload(UserRepoToPerm.repository),
2077 joinedload(UserRepoToPerm.user),
2095 joinedload(UserRepoToPerm.user),
2078 joinedload(UserRepoToPerm.permission),)
2096 joinedload(UserRepoToPerm.permission),)
2079
2097
2080 # get owners and admins and permissions. We do a trick of re-writing
2098 # get owners and admins and permissions. We do a trick of re-writing
2081 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2099 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2082 # has a global reference and changing one object propagates to all
2100 # has a global reference and changing one object propagates to all
2083 # others. This means if admin is also an owner admin_row that change
2101 # others. This means if admin is also an owner admin_row that change
2084 # would propagate to both objects
2102 # would propagate to both objects
2085 perm_rows = []
2103 perm_rows = []
2086 for _usr in q.all():
2104 for _usr in q.all():
2087 usr = AttributeDict(_usr.user.get_dict())
2105 usr = AttributeDict(_usr.user.get_dict())
2088 # if this user is also owner/admin, mark as duplicate record
2106 # if this user is also owner/admin, mark as duplicate record
2089 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2107 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2090 usr.duplicate_perm = True
2108 usr.duplicate_perm = True
2091 # also check if this permission is maybe used by branch_permissions
2109 # also check if this permission is maybe used by branch_permissions
2092 if _usr.branch_perm_entry:
2110 if _usr.branch_perm_entry:
2093 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2111 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2094
2112
2095 usr.permission = _usr.permission.permission_name
2113 usr.permission = _usr.permission.permission_name
2096 usr.permission_id = _usr.repo_to_perm_id
2114 usr.permission_id = _usr.repo_to_perm_id
2097 perm_rows.append(usr)
2115 perm_rows.append(usr)
2098
2116
2099 # filter the perm rows by 'default' first and then sort them by
2117 # filter the perm rows by 'default' first and then sort them by
2100 # admin,write,read,none permissions sorted again alphabetically in
2118 # admin,write,read,none permissions sorted again alphabetically in
2101 # each group
2119 # each group
2102 perm_rows = sorted(perm_rows, key=display_user_sort)
2120 perm_rows = sorted(perm_rows, key=display_user_sort)
2103
2121
2104 user_groups_rows = []
2122 user_groups_rows = []
2105 if expand_from_user_groups:
2123 if expand_from_user_groups:
2106 for ug in self.permission_user_groups(with_members=True):
2124 for ug in self.permission_user_groups(with_members=True):
2107 for user_data in ug.members:
2125 for user_data in ug.members:
2108 user_groups_rows.append(user_data)
2126 user_groups_rows.append(user_data)
2109
2127
2110 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2128 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2111
2129
2112 def permission_user_groups(self, with_members=True):
2130 def permission_user_groups(self, with_members=True):
2113 q = UserGroupRepoToPerm.query()\
2131 q = UserGroupRepoToPerm.query()\
2114 .filter(UserGroupRepoToPerm.repository == self)
2132 .filter(UserGroupRepoToPerm.repository == self)
2115 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2133 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2116 joinedload(UserGroupRepoToPerm.users_group),
2134 joinedload(UserGroupRepoToPerm.users_group),
2117 joinedload(UserGroupRepoToPerm.permission),)
2135 joinedload(UserGroupRepoToPerm.permission),)
2118
2136
2119 perm_rows = []
2137 perm_rows = []
2120 for _user_group in q.all():
2138 for _user_group in q.all():
2121 entry = AttributeDict(_user_group.users_group.get_dict())
2139 entry = AttributeDict(_user_group.users_group.get_dict())
2122 entry.permission = _user_group.permission.permission_name
2140 entry.permission = _user_group.permission.permission_name
2123 if with_members:
2141 if with_members:
2124 entry.members = [x.user.get_dict()
2142 entry.members = [x.user.get_dict()
2125 for x in _user_group.users_group.members]
2143 for x in _user_group.users_group.members]
2126 perm_rows.append(entry)
2144 perm_rows.append(entry)
2127
2145
2128 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2146 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2129 return perm_rows
2147 return perm_rows
2130
2148
2131 def get_api_data(self, include_secrets=False):
2149 def get_api_data(self, include_secrets=False):
2132 """
2150 """
2133 Common function for generating repo api data
2151 Common function for generating repo api data
2134
2152
2135 :param include_secrets: See :meth:`User.get_api_data`.
2153 :param include_secrets: See :meth:`User.get_api_data`.
2136
2154
2137 """
2155 """
2138 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2156 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2139 # move this methods on models level.
2157 # move this methods on models level.
2140 from rhodecode.model.settings import SettingsModel
2158 from rhodecode.model.settings import SettingsModel
2141 from rhodecode.model.repo import RepoModel
2159 from rhodecode.model.repo import RepoModel
2142
2160
2143 repo = self
2161 repo = self
2144 _user_id, _time, _reason = self.locked
2162 _user_id, _time, _reason = self.locked
2145
2163
2146 data = {
2164 data = {
2147 'repo_id': repo.repo_id,
2165 'repo_id': repo.repo_id,
2148 'repo_name': repo.repo_name,
2166 'repo_name': repo.repo_name,
2149 'repo_type': repo.repo_type,
2167 'repo_type': repo.repo_type,
2150 'clone_uri': repo.clone_uri or '',
2168 'clone_uri': repo.clone_uri or '',
2151 'push_uri': repo.push_uri or '',
2169 'push_uri': repo.push_uri or '',
2152 'url': RepoModel().get_url(self),
2170 'url': RepoModel().get_url(self),
2153 'private': repo.private,
2171 'private': repo.private,
2154 'created_on': repo.created_on,
2172 'created_on': repo.created_on,
2155 'description': repo.description_safe,
2173 'description': repo.description_safe,
2156 'landing_rev': repo.landing_rev,
2174 'landing_rev': repo.landing_rev,
2157 'owner': repo.user.username,
2175 'owner': repo.user.username,
2158 'fork_of': repo.fork.repo_name if repo.fork else None,
2176 'fork_of': repo.fork.repo_name if repo.fork else None,
2159 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2177 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2160 'enable_statistics': repo.enable_statistics,
2178 'enable_statistics': repo.enable_statistics,
2161 'enable_locking': repo.enable_locking,
2179 'enable_locking': repo.enable_locking,
2162 'enable_downloads': repo.enable_downloads,
2180 'enable_downloads': repo.enable_downloads,
2163 'last_changeset': repo.changeset_cache,
2181 'last_changeset': repo.changeset_cache,
2164 'locked_by': User.get(_user_id).get_api_data(
2182 'locked_by': User.get(_user_id).get_api_data(
2165 include_secrets=include_secrets) if _user_id else None,
2183 include_secrets=include_secrets) if _user_id else None,
2166 'locked_date': time_to_datetime(_time) if _time else None,
2184 'locked_date': time_to_datetime(_time) if _time else None,
2167 'lock_reason': _reason if _reason else None,
2185 'lock_reason': _reason if _reason else None,
2168 }
2186 }
2169
2187
2170 # TODO: mikhail: should be per-repo settings here
2188 # TODO: mikhail: should be per-repo settings here
2171 rc_config = SettingsModel().get_all_settings()
2189 rc_config = SettingsModel().get_all_settings()
2172 repository_fields = str2bool(
2190 repository_fields = str2bool(
2173 rc_config.get('rhodecode_repository_fields'))
2191 rc_config.get('rhodecode_repository_fields'))
2174 if repository_fields:
2192 if repository_fields:
2175 for f in self.extra_fields:
2193 for f in self.extra_fields:
2176 data[f.field_key_prefixed] = f.field_value
2194 data[f.field_key_prefixed] = f.field_value
2177
2195
2178 return data
2196 return data
2179
2197
2180 @classmethod
2198 @classmethod
2181 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2199 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2182 if not lock_time:
2200 if not lock_time:
2183 lock_time = time.time()
2201 lock_time = time.time()
2184 if not lock_reason:
2202 if not lock_reason:
2185 lock_reason = cls.LOCK_AUTOMATIC
2203 lock_reason = cls.LOCK_AUTOMATIC
2186 repo.locked = [user_id, lock_time, lock_reason]
2204 repo.locked = [user_id, lock_time, lock_reason]
2187 Session().add(repo)
2205 Session().add(repo)
2188 Session().commit()
2206 Session().commit()
2189
2207
2190 @classmethod
2208 @classmethod
2191 def unlock(cls, repo):
2209 def unlock(cls, repo):
2192 repo.locked = None
2210 repo.locked = None
2193 Session().add(repo)
2211 Session().add(repo)
2194 Session().commit()
2212 Session().commit()
2195
2213
2196 @classmethod
2214 @classmethod
2197 def getlock(cls, repo):
2215 def getlock(cls, repo):
2198 return repo.locked
2216 return repo.locked
2199
2217
2200 def is_user_lock(self, user_id):
2218 def is_user_lock(self, user_id):
2201 if self.lock[0]:
2219 if self.lock[0]:
2202 lock_user_id = safe_int(self.lock[0])
2220 lock_user_id = safe_int(self.lock[0])
2203 user_id = safe_int(user_id)
2221 user_id = safe_int(user_id)
2204 # both are ints, and they are equal
2222 # both are ints, and they are equal
2205 return all([lock_user_id, user_id]) and lock_user_id == user_id
2223 return all([lock_user_id, user_id]) and lock_user_id == user_id
2206
2224
2207 return False
2225 return False
2208
2226
2209 def get_locking_state(self, action, user_id, only_when_enabled=True):
2227 def get_locking_state(self, action, user_id, only_when_enabled=True):
2210 """
2228 """
2211 Checks locking on this repository, if locking is enabled and lock is
2229 Checks locking on this repository, if locking is enabled and lock is
2212 present returns a tuple of make_lock, locked, locked_by.
2230 present returns a tuple of make_lock, locked, locked_by.
2213 make_lock can have 3 states None (do nothing) True, make lock
2231 make_lock can have 3 states None (do nothing) True, make lock
2214 False release lock, This value is later propagated to hooks, which
2232 False release lock, This value is later propagated to hooks, which
2215 do the locking. Think about this as signals passed to hooks what to do.
2233 do the locking. Think about this as signals passed to hooks what to do.
2216
2234
2217 """
2235 """
2218 # TODO: johbo: This is part of the business logic and should be moved
2236 # TODO: johbo: This is part of the business logic and should be moved
2219 # into the RepositoryModel.
2237 # into the RepositoryModel.
2220
2238
2221 if action not in ('push', 'pull'):
2239 if action not in ('push', 'pull'):
2222 raise ValueError("Invalid action value: %s" % repr(action))
2240 raise ValueError("Invalid action value: %s" % repr(action))
2223
2241
2224 # defines if locked error should be thrown to user
2242 # defines if locked error should be thrown to user
2225 currently_locked = False
2243 currently_locked = False
2226 # defines if new lock should be made, tri-state
2244 # defines if new lock should be made, tri-state
2227 make_lock = None
2245 make_lock = None
2228 repo = self
2246 repo = self
2229 user = User.get(user_id)
2247 user = User.get(user_id)
2230
2248
2231 lock_info = repo.locked
2249 lock_info = repo.locked
2232
2250
2233 if repo and (repo.enable_locking or not only_when_enabled):
2251 if repo and (repo.enable_locking or not only_when_enabled):
2234 if action == 'push':
2252 if action == 'push':
2235 # check if it's already locked !, if it is compare users
2253 # check if it's already locked !, if it is compare users
2236 locked_by_user_id = lock_info[0]
2254 locked_by_user_id = lock_info[0]
2237 if user.user_id == locked_by_user_id:
2255 if user.user_id == locked_by_user_id:
2238 log.debug(
2256 log.debug(
2239 'Got `push` action from user %s, now unlocking', user)
2257 'Got `push` action from user %s, now unlocking', user)
2240 # unlock if we have push from user who locked
2258 # unlock if we have push from user who locked
2241 make_lock = False
2259 make_lock = False
2242 else:
2260 else:
2243 # we're not the same user who locked, ban with
2261 # we're not the same user who locked, ban with
2244 # code defined in settings (default is 423 HTTP Locked) !
2262 # code defined in settings (default is 423 HTTP Locked) !
2245 log.debug('Repo %s is currently locked by %s', repo, user)
2263 log.debug('Repo %s is currently locked by %s', repo, user)
2246 currently_locked = True
2264 currently_locked = True
2247 elif action == 'pull':
2265 elif action == 'pull':
2248 # [0] user [1] date
2266 # [0] user [1] date
2249 if lock_info[0] and lock_info[1]:
2267 if lock_info[0] and lock_info[1]:
2250 log.debug('Repo %s is currently locked by %s', repo, user)
2268 log.debug('Repo %s is currently locked by %s', repo, user)
2251 currently_locked = True
2269 currently_locked = True
2252 else:
2270 else:
2253 log.debug('Setting lock on repo %s by %s', repo, user)
2271 log.debug('Setting lock on repo %s by %s', repo, user)
2254 make_lock = True
2272 make_lock = True
2255
2273
2256 else:
2274 else:
2257 log.debug('Repository %s do not have locking enabled', repo)
2275 log.debug('Repository %s do not have locking enabled', repo)
2258
2276
2259 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2277 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2260 make_lock, currently_locked, lock_info)
2278 make_lock, currently_locked, lock_info)
2261
2279
2262 from rhodecode.lib.auth import HasRepoPermissionAny
2280 from rhodecode.lib.auth import HasRepoPermissionAny
2263 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2281 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2264 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2282 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2265 # if we don't have at least write permission we cannot make a lock
2283 # if we don't have at least write permission we cannot make a lock
2266 log.debug('lock state reset back to FALSE due to lack '
2284 log.debug('lock state reset back to FALSE due to lack '
2267 'of at least read permission')
2285 'of at least read permission')
2268 make_lock = False
2286 make_lock = False
2269
2287
2270 return make_lock, currently_locked, lock_info
2288 return make_lock, currently_locked, lock_info
2271
2289
2272 @property
2290 @property
2273 def last_commit_cache_update_diff(self):
2291 def last_commit_cache_update_diff(self):
2274 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2292 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2275
2293
2276 @classmethod
2294 @classmethod
2277 def _load_commit_change(cls, last_commit_cache):
2295 def _load_commit_change(cls, last_commit_cache):
2278 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2296 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2279 empty_date = datetime.datetime.fromtimestamp(0)
2297 empty_date = datetime.datetime.fromtimestamp(0)
2280 date_latest = last_commit_cache.get('date', empty_date)
2298 date_latest = last_commit_cache.get('date', empty_date)
2281 try:
2299 try:
2282 return parse_datetime(date_latest)
2300 return parse_datetime(date_latest)
2283 except Exception:
2301 except Exception:
2284 return empty_date
2302 return empty_date
2285
2303
2286 @property
2304 @property
2287 def last_commit_change(self):
2305 def last_commit_change(self):
2288 return self._load_commit_change(self.changeset_cache)
2306 return self._load_commit_change(self.changeset_cache)
2289
2307
2290 @property
2308 @property
2291 def last_db_change(self):
2309 def last_db_change(self):
2292 return self.updated_on
2310 return self.updated_on
2293
2311
2294 @property
2312 @property
2295 def clone_uri_hidden(self):
2313 def clone_uri_hidden(self):
2296 clone_uri = self.clone_uri
2314 clone_uri = self.clone_uri
2297 if clone_uri:
2315 if clone_uri:
2298 import urlobject
2316 import urlobject
2299 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2317 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2300 if url_obj.password:
2318 if url_obj.password:
2301 clone_uri = url_obj.with_password('*****')
2319 clone_uri = url_obj.with_password('*****')
2302 return clone_uri
2320 return clone_uri
2303
2321
2304 @property
2322 @property
2305 def push_uri_hidden(self):
2323 def push_uri_hidden(self):
2306 push_uri = self.push_uri
2324 push_uri = self.push_uri
2307 if push_uri:
2325 if push_uri:
2308 import urlobject
2326 import urlobject
2309 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2327 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2310 if url_obj.password:
2328 if url_obj.password:
2311 push_uri = url_obj.with_password('*****')
2329 push_uri = url_obj.with_password('*****')
2312 return push_uri
2330 return push_uri
2313
2331
2314 def clone_url(self, **override):
2332 def clone_url(self, **override):
2315 from rhodecode.model.settings import SettingsModel
2333 from rhodecode.model.settings import SettingsModel
2316
2334
2317 uri_tmpl = None
2335 uri_tmpl = None
2318 if 'with_id' in override:
2336 if 'with_id' in override:
2319 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2337 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2320 del override['with_id']
2338 del override['with_id']
2321
2339
2322 if 'uri_tmpl' in override:
2340 if 'uri_tmpl' in override:
2323 uri_tmpl = override['uri_tmpl']
2341 uri_tmpl = override['uri_tmpl']
2324 del override['uri_tmpl']
2342 del override['uri_tmpl']
2325
2343
2326 ssh = False
2344 ssh = False
2327 if 'ssh' in override:
2345 if 'ssh' in override:
2328 ssh = True
2346 ssh = True
2329 del override['ssh']
2347 del override['ssh']
2330
2348
2331 # we didn't override our tmpl from **overrides
2349 # we didn't override our tmpl from **overrides
2332 request = get_current_request()
2350 request = get_current_request()
2333 if not uri_tmpl:
2351 if not uri_tmpl:
2334 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2352 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2335 rc_config = request.call_context.rc_config
2353 rc_config = request.call_context.rc_config
2336 else:
2354 else:
2337 rc_config = SettingsModel().get_all_settings(cache=True)
2355 rc_config = SettingsModel().get_all_settings(cache=True)
2338
2356
2339 if ssh:
2357 if ssh:
2340 uri_tmpl = rc_config.get(
2358 uri_tmpl = rc_config.get(
2341 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2359 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2342
2360
2343 else:
2361 else:
2344 uri_tmpl = rc_config.get(
2362 uri_tmpl = rc_config.get(
2345 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2363 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2346
2364
2347 return get_clone_url(request=request,
2365 return get_clone_url(request=request,
2348 uri_tmpl=uri_tmpl,
2366 uri_tmpl=uri_tmpl,
2349 repo_name=self.repo_name,
2367 repo_name=self.repo_name,
2350 repo_id=self.repo_id,
2368 repo_id=self.repo_id,
2351 repo_type=self.repo_type,
2369 repo_type=self.repo_type,
2352 **override)
2370 **override)
2353
2371
2354 def set_state(self, state):
2372 def set_state(self, state):
2355 self.repo_state = state
2373 self.repo_state = state
2356 Session().add(self)
2374 Session().add(self)
2357 #==========================================================================
2375 #==========================================================================
2358 # SCM PROPERTIES
2376 # SCM PROPERTIES
2359 #==========================================================================
2377 #==========================================================================
2360
2378
2361 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2379 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2362 return get_commit_safe(
2380 return get_commit_safe(
2363 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2381 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2364 maybe_unreachable=maybe_unreachable)
2382 maybe_unreachable=maybe_unreachable)
2365
2383
2366 def get_changeset(self, rev=None, pre_load=None):
2384 def get_changeset(self, rev=None, pre_load=None):
2367 warnings.warn("Use get_commit", DeprecationWarning)
2385 warnings.warn("Use get_commit", DeprecationWarning)
2368 commit_id = None
2386 commit_id = None
2369 commit_idx = None
2387 commit_idx = None
2370 if isinstance(rev, compat.string_types):
2388 if isinstance(rev, compat.string_types):
2371 commit_id = rev
2389 commit_id = rev
2372 else:
2390 else:
2373 commit_idx = rev
2391 commit_idx = rev
2374 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2392 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2375 pre_load=pre_load)
2393 pre_load=pre_load)
2376
2394
2377 def get_landing_commit(self):
2395 def get_landing_commit(self):
2378 """
2396 """
2379 Returns landing commit, or if that doesn't exist returns the tip
2397 Returns landing commit, or if that doesn't exist returns the tip
2380 """
2398 """
2381 _rev_type, _rev = self.landing_rev
2399 _rev_type, _rev = self.landing_rev
2382 commit = self.get_commit(_rev)
2400 commit = self.get_commit(_rev)
2383 if isinstance(commit, EmptyCommit):
2401 if isinstance(commit, EmptyCommit):
2384 return self.get_commit()
2402 return self.get_commit()
2385 return commit
2403 return commit
2386
2404
2387 def flush_commit_cache(self):
2405 def flush_commit_cache(self):
2388 self.update_commit_cache(cs_cache={'raw_id':'0'})
2406 self.update_commit_cache(cs_cache={'raw_id':'0'})
2389 self.update_commit_cache()
2407 self.update_commit_cache()
2390
2408
2391 def update_commit_cache(self, cs_cache=None, config=None):
2409 def update_commit_cache(self, cs_cache=None, config=None):
2392 """
2410 """
2393 Update cache of last commit for repository
2411 Update cache of last commit for repository
2394 cache_keys should be::
2412 cache_keys should be::
2395
2413
2396 source_repo_id
2414 source_repo_id
2397 short_id
2415 short_id
2398 raw_id
2416 raw_id
2399 revision
2417 revision
2400 parents
2418 parents
2401 message
2419 message
2402 date
2420 date
2403 author
2421 author
2404 updated_on
2422 updated_on
2405
2423
2406 """
2424 """
2407 from rhodecode.lib.vcs.backends.base import BaseChangeset
2425 from rhodecode.lib.vcs.backends.base import BaseChangeset
2408 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2426 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2409 empty_date = datetime.datetime.fromtimestamp(0)
2427 empty_date = datetime.datetime.fromtimestamp(0)
2410
2428
2411 if cs_cache is None:
2429 if cs_cache is None:
2412 # use no-cache version here
2430 # use no-cache version here
2413 try:
2431 try:
2414 scm_repo = self.scm_instance(cache=False, config=config)
2432 scm_repo = self.scm_instance(cache=False, config=config)
2415 except VCSError:
2433 except VCSError:
2416 scm_repo = None
2434 scm_repo = None
2417 empty = scm_repo is None or scm_repo.is_empty()
2435 empty = scm_repo is None or scm_repo.is_empty()
2418
2436
2419 if not empty:
2437 if not empty:
2420 cs_cache = scm_repo.get_commit(
2438 cs_cache = scm_repo.get_commit(
2421 pre_load=["author", "date", "message", "parents", "branch"])
2439 pre_load=["author", "date", "message", "parents", "branch"])
2422 else:
2440 else:
2423 cs_cache = EmptyCommit()
2441 cs_cache = EmptyCommit()
2424
2442
2425 if isinstance(cs_cache, BaseChangeset):
2443 if isinstance(cs_cache, BaseChangeset):
2426 cs_cache = cs_cache.__json__()
2444 cs_cache = cs_cache.__json__()
2427
2445
2428 def is_outdated(new_cs_cache):
2446 def is_outdated(new_cs_cache):
2429 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2447 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2430 new_cs_cache['revision'] != self.changeset_cache['revision']):
2448 new_cs_cache['revision'] != self.changeset_cache['revision']):
2431 return True
2449 return True
2432 return False
2450 return False
2433
2451
2434 # check if we have maybe already latest cached revision
2452 # check if we have maybe already latest cached revision
2435 if is_outdated(cs_cache) or not self.changeset_cache:
2453 if is_outdated(cs_cache) or not self.changeset_cache:
2436 _current_datetime = datetime.datetime.utcnow()
2454 _current_datetime = datetime.datetime.utcnow()
2437 last_change = cs_cache.get('date') or _current_datetime
2455 last_change = cs_cache.get('date') or _current_datetime
2438 # we check if last update is newer than the new value
2456 # we check if last update is newer than the new value
2439 # if yes, we use the current timestamp instead. Imagine you get
2457 # if yes, we use the current timestamp instead. Imagine you get
2440 # old commit pushed 1y ago, we'd set last update 1y to ago.
2458 # old commit pushed 1y ago, we'd set last update 1y to ago.
2441 last_change_timestamp = datetime_to_time(last_change)
2459 last_change_timestamp = datetime_to_time(last_change)
2442 current_timestamp = datetime_to_time(last_change)
2460 current_timestamp = datetime_to_time(last_change)
2443 if last_change_timestamp > current_timestamp and not empty:
2461 if last_change_timestamp > current_timestamp and not empty:
2444 cs_cache['date'] = _current_datetime
2462 cs_cache['date'] = _current_datetime
2445
2463
2446 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2464 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2447 cs_cache['updated_on'] = time.time()
2465 cs_cache['updated_on'] = time.time()
2448 self.changeset_cache = cs_cache
2466 self.changeset_cache = cs_cache
2449 self.updated_on = last_change
2467 self.updated_on = last_change
2450 Session().add(self)
2468 Session().add(self)
2451 Session().commit()
2469 Session().commit()
2452
2470
2453 else:
2471 else:
2454 if empty:
2472 if empty:
2455 cs_cache = EmptyCommit().__json__()
2473 cs_cache = EmptyCommit().__json__()
2456 else:
2474 else:
2457 cs_cache = self.changeset_cache
2475 cs_cache = self.changeset_cache
2458
2476
2459 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2477 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2460
2478
2461 cs_cache['updated_on'] = time.time()
2479 cs_cache['updated_on'] = time.time()
2462 self.changeset_cache = cs_cache
2480 self.changeset_cache = cs_cache
2463 self.updated_on = _date_latest
2481 self.updated_on = _date_latest
2464 Session().add(self)
2482 Session().add(self)
2465 Session().commit()
2483 Session().commit()
2466
2484
2467 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2485 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2468 self.repo_name, cs_cache, _date_latest)
2486 self.repo_name, cs_cache, _date_latest)
2469
2487
2470 @property
2488 @property
2471 def tip(self):
2489 def tip(self):
2472 return self.get_commit('tip')
2490 return self.get_commit('tip')
2473
2491
2474 @property
2492 @property
2475 def author(self):
2493 def author(self):
2476 return self.tip.author
2494 return self.tip.author
2477
2495
2478 @property
2496 @property
2479 def last_change(self):
2497 def last_change(self):
2480 return self.scm_instance().last_change
2498 return self.scm_instance().last_change
2481
2499
2482 def get_comments(self, revisions=None):
2500 def get_comments(self, revisions=None):
2483 """
2501 """
2484 Returns comments for this repository grouped by revisions
2502 Returns comments for this repository grouped by revisions
2485
2503
2486 :param revisions: filter query by revisions only
2504 :param revisions: filter query by revisions only
2487 """
2505 """
2488 cmts = ChangesetComment.query()\
2506 cmts = ChangesetComment.query()\
2489 .filter(ChangesetComment.repo == self)
2507 .filter(ChangesetComment.repo == self)
2490 if revisions:
2508 if revisions:
2491 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2509 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2492 grouped = collections.defaultdict(list)
2510 grouped = collections.defaultdict(list)
2493 for cmt in cmts.all():
2511 for cmt in cmts.all():
2494 grouped[cmt.revision].append(cmt)
2512 grouped[cmt.revision].append(cmt)
2495 return grouped
2513 return grouped
2496
2514
2497 def statuses(self, revisions=None):
2515 def statuses(self, revisions=None):
2498 """
2516 """
2499 Returns statuses for this repository
2517 Returns statuses for this repository
2500
2518
2501 :param revisions: list of revisions to get statuses for
2519 :param revisions: list of revisions to get statuses for
2502 """
2520 """
2503 statuses = ChangesetStatus.query()\
2521 statuses = ChangesetStatus.query()\
2504 .filter(ChangesetStatus.repo == self)\
2522 .filter(ChangesetStatus.repo == self)\
2505 .filter(ChangesetStatus.version == 0)
2523 .filter(ChangesetStatus.version == 0)
2506
2524
2507 if revisions:
2525 if revisions:
2508 # Try doing the filtering in chunks to avoid hitting limits
2526 # Try doing the filtering in chunks to avoid hitting limits
2509 size = 500
2527 size = 500
2510 status_results = []
2528 status_results = []
2511 for chunk in xrange(0, len(revisions), size):
2529 for chunk in xrange(0, len(revisions), size):
2512 status_results += statuses.filter(
2530 status_results += statuses.filter(
2513 ChangesetStatus.revision.in_(
2531 ChangesetStatus.revision.in_(
2514 revisions[chunk: chunk+size])
2532 revisions[chunk: chunk+size])
2515 ).all()
2533 ).all()
2516 else:
2534 else:
2517 status_results = statuses.all()
2535 status_results = statuses.all()
2518
2536
2519 grouped = {}
2537 grouped = {}
2520
2538
2521 # maybe we have open new pullrequest without a status?
2539 # maybe we have open new pullrequest without a status?
2522 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2540 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2523 status_lbl = ChangesetStatus.get_status_lbl(stat)
2541 status_lbl = ChangesetStatus.get_status_lbl(stat)
2524 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2542 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2525 for rev in pr.revisions:
2543 for rev in pr.revisions:
2526 pr_id = pr.pull_request_id
2544 pr_id = pr.pull_request_id
2527 pr_repo = pr.target_repo.repo_name
2545 pr_repo = pr.target_repo.repo_name
2528 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2546 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2529
2547
2530 for stat in status_results:
2548 for stat in status_results:
2531 pr_id = pr_repo = None
2549 pr_id = pr_repo = None
2532 if stat.pull_request:
2550 if stat.pull_request:
2533 pr_id = stat.pull_request.pull_request_id
2551 pr_id = stat.pull_request.pull_request_id
2534 pr_repo = stat.pull_request.target_repo.repo_name
2552 pr_repo = stat.pull_request.target_repo.repo_name
2535 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2553 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2536 pr_id, pr_repo]
2554 pr_id, pr_repo]
2537 return grouped
2555 return grouped
2538
2556
2539 # ==========================================================================
2557 # ==========================================================================
2540 # SCM CACHE INSTANCE
2558 # SCM CACHE INSTANCE
2541 # ==========================================================================
2559 # ==========================================================================
2542
2560
2543 def scm_instance(self, **kwargs):
2561 def scm_instance(self, **kwargs):
2544 import rhodecode
2562 import rhodecode
2545
2563
2546 # Passing a config will not hit the cache currently only used
2564 # Passing a config will not hit the cache currently only used
2547 # for repo2dbmapper
2565 # for repo2dbmapper
2548 config = kwargs.pop('config', None)
2566 config = kwargs.pop('config', None)
2549 cache = kwargs.pop('cache', None)
2567 cache = kwargs.pop('cache', None)
2550 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2568 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2551 if vcs_full_cache is not None:
2569 if vcs_full_cache is not None:
2552 # allows override global config
2570 # allows override global config
2553 full_cache = vcs_full_cache
2571 full_cache = vcs_full_cache
2554 else:
2572 else:
2555 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2573 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2556 # if cache is NOT defined use default global, else we have a full
2574 # if cache is NOT defined use default global, else we have a full
2557 # control over cache behaviour
2575 # control over cache behaviour
2558 if cache is None and full_cache and not config:
2576 if cache is None and full_cache and not config:
2559 log.debug('Initializing pure cached instance for %s', self.repo_path)
2577 log.debug('Initializing pure cached instance for %s', self.repo_path)
2560 return self._get_instance_cached()
2578 return self._get_instance_cached()
2561
2579
2562 # cache here is sent to the "vcs server"
2580 # cache here is sent to the "vcs server"
2563 return self._get_instance(cache=bool(cache), config=config)
2581 return self._get_instance(cache=bool(cache), config=config)
2564
2582
2565 def _get_instance_cached(self):
2583 def _get_instance_cached(self):
2566 from rhodecode.lib import rc_cache
2584 from rhodecode.lib import rc_cache
2567
2585
2568 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2586 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2569 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2587 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2570 repo_id=self.repo_id)
2588 repo_id=self.repo_id)
2571 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2589 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2572
2590
2573 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2591 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2574 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2592 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2575 return self._get_instance(repo_state_uid=_cache_state_uid)
2593 return self._get_instance(repo_state_uid=_cache_state_uid)
2576
2594
2577 # we must use thread scoped cache here,
2595 # we must use thread scoped cache here,
2578 # because each thread of gevent needs it's own not shared connection and cache
2596 # because each thread of gevent needs it's own not shared connection and cache
2579 # we also alter `args` so the cache key is individual for every green thread.
2597 # we also alter `args` so the cache key is individual for every green thread.
2580 inv_context_manager = rc_cache.InvalidationContext(
2598 inv_context_manager = rc_cache.InvalidationContext(
2581 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2599 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2582 thread_scoped=True)
2600 thread_scoped=True)
2583 with inv_context_manager as invalidation_context:
2601 with inv_context_manager as invalidation_context:
2584 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2602 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2585 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2603 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2586
2604
2587 # re-compute and store cache if we get invalidate signal
2605 # re-compute and store cache if we get invalidate signal
2588 if invalidation_context.should_invalidate():
2606 if invalidation_context.should_invalidate():
2589 instance = get_instance_cached.refresh(*args)
2607 instance = get_instance_cached.refresh(*args)
2590 else:
2608 else:
2591 instance = get_instance_cached(*args)
2609 instance = get_instance_cached(*args)
2592
2610
2593 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2611 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2594 return instance
2612 return instance
2595
2613
2596 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2614 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2597 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2615 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2598 self.repo_type, self.repo_path, cache)
2616 self.repo_type, self.repo_path, cache)
2599 config = config or self._config
2617 config = config or self._config
2600 custom_wire = {
2618 custom_wire = {
2601 'cache': cache, # controls the vcs.remote cache
2619 'cache': cache, # controls the vcs.remote cache
2602 'repo_state_uid': repo_state_uid
2620 'repo_state_uid': repo_state_uid
2603 }
2621 }
2604 repo = get_vcs_instance(
2622 repo = get_vcs_instance(
2605 repo_path=safe_str(self.repo_full_path),
2623 repo_path=safe_str(self.repo_full_path),
2606 config=config,
2624 config=config,
2607 with_wire=custom_wire,
2625 with_wire=custom_wire,
2608 create=False,
2626 create=False,
2609 _vcs_alias=self.repo_type)
2627 _vcs_alias=self.repo_type)
2610 if repo is not None:
2628 if repo is not None:
2611 repo.count() # cache rebuild
2629 repo.count() # cache rebuild
2612 return repo
2630 return repo
2613
2631
2614 def get_shadow_repository_path(self, workspace_id):
2632 def get_shadow_repository_path(self, workspace_id):
2615 from rhodecode.lib.vcs.backends.base import BaseRepository
2633 from rhodecode.lib.vcs.backends.base import BaseRepository
2616 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2634 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2617 self.repo_full_path, self.repo_id, workspace_id)
2635 self.repo_full_path, self.repo_id, workspace_id)
2618 return shadow_repo_path
2636 return shadow_repo_path
2619
2637
2620 def __json__(self):
2638 def __json__(self):
2621 return {'landing_rev': self.landing_rev}
2639 return {'landing_rev': self.landing_rev}
2622
2640
2623 def get_dict(self):
2641 def get_dict(self):
2624
2642
2625 # Since we transformed `repo_name` to a hybrid property, we need to
2643 # Since we transformed `repo_name` to a hybrid property, we need to
2626 # keep compatibility with the code which uses `repo_name` field.
2644 # keep compatibility with the code which uses `repo_name` field.
2627
2645
2628 result = super(Repository, self).get_dict()
2646 result = super(Repository, self).get_dict()
2629 result['repo_name'] = result.pop('_repo_name', None)
2647 result['repo_name'] = result.pop('_repo_name', None)
2630 return result
2648 return result
2631
2649
2632
2650
2633 class RepoGroup(Base, BaseModel):
2651 class RepoGroup(Base, BaseModel):
2634 __tablename__ = 'groups'
2652 __tablename__ = 'groups'
2635 __table_args__ = (
2653 __table_args__ = (
2636 UniqueConstraint('group_name', 'group_parent_id'),
2654 UniqueConstraint('group_name', 'group_parent_id'),
2637 base_table_args,
2655 base_table_args,
2638 )
2656 )
2639 __mapper_args__ = {'order_by': 'group_name'}
2657 __mapper_args__ = {'order_by': 'group_name'}
2640
2658
2641 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2659 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2642
2660
2643 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2661 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2644 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2662 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2645 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2663 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2646 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2664 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2647 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2665 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2648 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2666 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2649 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2667 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2650 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2668 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2651 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2669 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2652 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2670 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2653 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2671 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2654
2672
2655 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2673 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2656 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2674 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2657 parent_group = relationship('RepoGroup', remote_side=group_id)
2675 parent_group = relationship('RepoGroup', remote_side=group_id)
2658 user = relationship('User')
2676 user = relationship('User')
2659 integrations = relationship('Integration', cascade="all, delete-orphan")
2677 integrations = relationship('Integration', cascade="all, delete-orphan")
2660
2678
2661 # no cascade, set NULL
2679 # no cascade, set NULL
2662 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2680 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2663
2681
2664 def __init__(self, group_name='', parent_group=None):
2682 def __init__(self, group_name='', parent_group=None):
2665 self.group_name = group_name
2683 self.group_name = group_name
2666 self.parent_group = parent_group
2684 self.parent_group = parent_group
2667
2685
2668 def __unicode__(self):
2686 def __unicode__(self):
2669 return u"<%s('id:%s:%s')>" % (
2687 return u"<%s('id:%s:%s')>" % (
2670 self.__class__.__name__, self.group_id, self.group_name)
2688 self.__class__.__name__, self.group_id, self.group_name)
2671
2689
2672 @hybrid_property
2690 @hybrid_property
2673 def group_name(self):
2691 def group_name(self):
2674 return self._group_name
2692 return self._group_name
2675
2693
2676 @group_name.setter
2694 @group_name.setter
2677 def group_name(self, value):
2695 def group_name(self, value):
2678 self._group_name = value
2696 self._group_name = value
2679 self.group_name_hash = self.hash_repo_group_name(value)
2697 self.group_name_hash = self.hash_repo_group_name(value)
2680
2698
2681 @classmethod
2699 @classmethod
2682 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2700 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2683 from rhodecode.lib.vcs.backends.base import EmptyCommit
2701 from rhodecode.lib.vcs.backends.base import EmptyCommit
2684 dummy = EmptyCommit().__json__()
2702 dummy = EmptyCommit().__json__()
2685 if not changeset_cache_raw:
2703 if not changeset_cache_raw:
2686 dummy['source_repo_id'] = repo_id
2704 dummy['source_repo_id'] = repo_id
2687 return json.loads(json.dumps(dummy))
2705 return json.loads(json.dumps(dummy))
2688
2706
2689 try:
2707 try:
2690 return json.loads(changeset_cache_raw)
2708 return json.loads(changeset_cache_raw)
2691 except TypeError:
2709 except TypeError:
2692 return dummy
2710 return dummy
2693 except Exception:
2711 except Exception:
2694 log.error(traceback.format_exc())
2712 log.error(traceback.format_exc())
2695 return dummy
2713 return dummy
2696
2714
2697 @hybrid_property
2715 @hybrid_property
2698 def changeset_cache(self):
2716 def changeset_cache(self):
2699 return self._load_changeset_cache('', self._changeset_cache)
2717 return self._load_changeset_cache('', self._changeset_cache)
2700
2718
2701 @changeset_cache.setter
2719 @changeset_cache.setter
2702 def changeset_cache(self, val):
2720 def changeset_cache(self, val):
2703 try:
2721 try:
2704 self._changeset_cache = json.dumps(val)
2722 self._changeset_cache = json.dumps(val)
2705 except Exception:
2723 except Exception:
2706 log.error(traceback.format_exc())
2724 log.error(traceback.format_exc())
2707
2725
2708 @validates('group_parent_id')
2726 @validates('group_parent_id')
2709 def validate_group_parent_id(self, key, val):
2727 def validate_group_parent_id(self, key, val):
2710 """
2728 """
2711 Check cycle references for a parent group to self
2729 Check cycle references for a parent group to self
2712 """
2730 """
2713 if self.group_id and val:
2731 if self.group_id and val:
2714 assert val != self.group_id
2732 assert val != self.group_id
2715
2733
2716 return val
2734 return val
2717
2735
2718 @hybrid_property
2736 @hybrid_property
2719 def description_safe(self):
2737 def description_safe(self):
2720 from rhodecode.lib import helpers as h
2738 from rhodecode.lib import helpers as h
2721 return h.escape(self.group_description)
2739 return h.escape(self.group_description)
2722
2740
2723 @classmethod
2741 @classmethod
2724 def hash_repo_group_name(cls, repo_group_name):
2742 def hash_repo_group_name(cls, repo_group_name):
2725 val = remove_formatting(repo_group_name)
2743 val = remove_formatting(repo_group_name)
2726 val = safe_str(val).lower()
2744 val = safe_str(val).lower()
2727 chars = []
2745 chars = []
2728 for c in val:
2746 for c in val:
2729 if c not in string.ascii_letters:
2747 if c not in string.ascii_letters:
2730 c = str(ord(c))
2748 c = str(ord(c))
2731 chars.append(c)
2749 chars.append(c)
2732
2750
2733 return ''.join(chars)
2751 return ''.join(chars)
2734
2752
2735 @classmethod
2753 @classmethod
2736 def _generate_choice(cls, repo_group):
2754 def _generate_choice(cls, repo_group):
2737 from webhelpers2.html import literal as _literal
2755 from webhelpers2.html import literal as _literal
2738 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2756 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2739 return repo_group.group_id, _name(repo_group.full_path_splitted)
2757 return repo_group.group_id, _name(repo_group.full_path_splitted)
2740
2758
2741 @classmethod
2759 @classmethod
2742 def groups_choices(cls, groups=None, show_empty_group=True):
2760 def groups_choices(cls, groups=None, show_empty_group=True):
2743 if not groups:
2761 if not groups:
2744 groups = cls.query().all()
2762 groups = cls.query().all()
2745
2763
2746 repo_groups = []
2764 repo_groups = []
2747 if show_empty_group:
2765 if show_empty_group:
2748 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2766 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2749
2767
2750 repo_groups.extend([cls._generate_choice(x) for x in groups])
2768 repo_groups.extend([cls._generate_choice(x) for x in groups])
2751
2769
2752 repo_groups = sorted(
2770 repo_groups = sorted(
2753 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2771 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2754 return repo_groups
2772 return repo_groups
2755
2773
2756 @classmethod
2774 @classmethod
2757 def url_sep(cls):
2775 def url_sep(cls):
2758 return URL_SEP
2776 return URL_SEP
2759
2777
2760 @classmethod
2778 @classmethod
2761 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2779 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2762 if case_insensitive:
2780 if case_insensitive:
2763 gr = cls.query().filter(func.lower(cls.group_name)
2781 gr = cls.query().filter(func.lower(cls.group_name)
2764 == func.lower(group_name))
2782 == func.lower(group_name))
2765 else:
2783 else:
2766 gr = cls.query().filter(cls.group_name == group_name)
2784 gr = cls.query().filter(cls.group_name == group_name)
2767 if cache:
2785 if cache:
2768 name_key = _hash_key(group_name)
2786 name_key = _hash_key(group_name)
2769 gr = gr.options(
2787 gr = gr.options(
2770 FromCache("sql_cache_short", "get_group_%s" % name_key))
2788 FromCache("sql_cache_short", "get_group_%s" % name_key))
2771 return gr.scalar()
2789 return gr.scalar()
2772
2790
2773 @classmethod
2791 @classmethod
2774 def get_user_personal_repo_group(cls, user_id):
2792 def get_user_personal_repo_group(cls, user_id):
2775 user = User.get(user_id)
2793 user = User.get(user_id)
2776 if user.username == User.DEFAULT_USER:
2794 if user.username == User.DEFAULT_USER:
2777 return None
2795 return None
2778
2796
2779 return cls.query()\
2797 return cls.query()\
2780 .filter(cls.personal == true()) \
2798 .filter(cls.personal == true()) \
2781 .filter(cls.user == user) \
2799 .filter(cls.user == user) \
2782 .order_by(cls.group_id.asc()) \
2800 .order_by(cls.group_id.asc()) \
2783 .first()
2801 .first()
2784
2802
2785 @classmethod
2803 @classmethod
2786 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2804 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2787 case_insensitive=True):
2805 case_insensitive=True):
2788 q = RepoGroup.query()
2806 q = RepoGroup.query()
2789
2807
2790 if not isinstance(user_id, Optional):
2808 if not isinstance(user_id, Optional):
2791 q = q.filter(RepoGroup.user_id == user_id)
2809 q = q.filter(RepoGroup.user_id == user_id)
2792
2810
2793 if not isinstance(group_id, Optional):
2811 if not isinstance(group_id, Optional):
2794 q = q.filter(RepoGroup.group_parent_id == group_id)
2812 q = q.filter(RepoGroup.group_parent_id == group_id)
2795
2813
2796 if case_insensitive:
2814 if case_insensitive:
2797 q = q.order_by(func.lower(RepoGroup.group_name))
2815 q = q.order_by(func.lower(RepoGroup.group_name))
2798 else:
2816 else:
2799 q = q.order_by(RepoGroup.group_name)
2817 q = q.order_by(RepoGroup.group_name)
2800 return q.all()
2818 return q.all()
2801
2819
2802 @property
2820 @property
2803 def parents(self, parents_recursion_limit=10):
2821 def parents(self, parents_recursion_limit=10):
2804 groups = []
2822 groups = []
2805 if self.parent_group is None:
2823 if self.parent_group is None:
2806 return groups
2824 return groups
2807 cur_gr = self.parent_group
2825 cur_gr = self.parent_group
2808 groups.insert(0, cur_gr)
2826 groups.insert(0, cur_gr)
2809 cnt = 0
2827 cnt = 0
2810 while 1:
2828 while 1:
2811 cnt += 1
2829 cnt += 1
2812 gr = getattr(cur_gr, 'parent_group', None)
2830 gr = getattr(cur_gr, 'parent_group', None)
2813 cur_gr = cur_gr.parent_group
2831 cur_gr = cur_gr.parent_group
2814 if gr is None:
2832 if gr is None:
2815 break
2833 break
2816 if cnt == parents_recursion_limit:
2834 if cnt == parents_recursion_limit:
2817 # this will prevent accidental infinit loops
2835 # this will prevent accidental infinit loops
2818 log.error('more than %s parents found for group %s, stopping '
2836 log.error('more than %s parents found for group %s, stopping '
2819 'recursive parent fetching', parents_recursion_limit, self)
2837 'recursive parent fetching', parents_recursion_limit, self)
2820 break
2838 break
2821
2839
2822 groups.insert(0, gr)
2840 groups.insert(0, gr)
2823 return groups
2841 return groups
2824
2842
2825 @property
2843 @property
2826 def last_commit_cache_update_diff(self):
2844 def last_commit_cache_update_diff(self):
2827 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2845 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2828
2846
2829 @classmethod
2847 @classmethod
2830 def _load_commit_change(cls, last_commit_cache):
2848 def _load_commit_change(cls, last_commit_cache):
2831 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2849 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2832 empty_date = datetime.datetime.fromtimestamp(0)
2850 empty_date = datetime.datetime.fromtimestamp(0)
2833 date_latest = last_commit_cache.get('date', empty_date)
2851 date_latest = last_commit_cache.get('date', empty_date)
2834 try:
2852 try:
2835 return parse_datetime(date_latest)
2853 return parse_datetime(date_latest)
2836 except Exception:
2854 except Exception:
2837 return empty_date
2855 return empty_date
2838
2856
2839 @property
2857 @property
2840 def last_commit_change(self):
2858 def last_commit_change(self):
2841 return self._load_commit_change(self.changeset_cache)
2859 return self._load_commit_change(self.changeset_cache)
2842
2860
2843 @property
2861 @property
2844 def last_db_change(self):
2862 def last_db_change(self):
2845 return self.updated_on
2863 return self.updated_on
2846
2864
2847 @property
2865 @property
2848 def children(self):
2866 def children(self):
2849 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2867 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2850
2868
2851 @property
2869 @property
2852 def name(self):
2870 def name(self):
2853 return self.group_name.split(RepoGroup.url_sep())[-1]
2871 return self.group_name.split(RepoGroup.url_sep())[-1]
2854
2872
2855 @property
2873 @property
2856 def full_path(self):
2874 def full_path(self):
2857 return self.group_name
2875 return self.group_name
2858
2876
2859 @property
2877 @property
2860 def full_path_splitted(self):
2878 def full_path_splitted(self):
2861 return self.group_name.split(RepoGroup.url_sep())
2879 return self.group_name.split(RepoGroup.url_sep())
2862
2880
2863 @property
2881 @property
2864 def repositories(self):
2882 def repositories(self):
2865 return Repository.query()\
2883 return Repository.query()\
2866 .filter(Repository.group == self)\
2884 .filter(Repository.group == self)\
2867 .order_by(Repository.repo_name)
2885 .order_by(Repository.repo_name)
2868
2886
2869 @property
2887 @property
2870 def repositories_recursive_count(self):
2888 def repositories_recursive_count(self):
2871 cnt = self.repositories.count()
2889 cnt = self.repositories.count()
2872
2890
2873 def children_count(group):
2891 def children_count(group):
2874 cnt = 0
2892 cnt = 0
2875 for child in group.children:
2893 for child in group.children:
2876 cnt += child.repositories.count()
2894 cnt += child.repositories.count()
2877 cnt += children_count(child)
2895 cnt += children_count(child)
2878 return cnt
2896 return cnt
2879
2897
2880 return cnt + children_count(self)
2898 return cnt + children_count(self)
2881
2899
2882 def _recursive_objects(self, include_repos=True, include_groups=True):
2900 def _recursive_objects(self, include_repos=True, include_groups=True):
2883 all_ = []
2901 all_ = []
2884
2902
2885 def _get_members(root_gr):
2903 def _get_members(root_gr):
2886 if include_repos:
2904 if include_repos:
2887 for r in root_gr.repositories:
2905 for r in root_gr.repositories:
2888 all_.append(r)
2906 all_.append(r)
2889 childs = root_gr.children.all()
2907 childs = root_gr.children.all()
2890 if childs:
2908 if childs:
2891 for gr in childs:
2909 for gr in childs:
2892 if include_groups:
2910 if include_groups:
2893 all_.append(gr)
2911 all_.append(gr)
2894 _get_members(gr)
2912 _get_members(gr)
2895
2913
2896 root_group = []
2914 root_group = []
2897 if include_groups:
2915 if include_groups:
2898 root_group = [self]
2916 root_group = [self]
2899
2917
2900 _get_members(self)
2918 _get_members(self)
2901 return root_group + all_
2919 return root_group + all_
2902
2920
2903 def recursive_groups_and_repos(self):
2921 def recursive_groups_and_repos(self):
2904 """
2922 """
2905 Recursive return all groups, with repositories in those groups
2923 Recursive return all groups, with repositories in those groups
2906 """
2924 """
2907 return self._recursive_objects()
2925 return self._recursive_objects()
2908
2926
2909 def recursive_groups(self):
2927 def recursive_groups(self):
2910 """
2928 """
2911 Returns all children groups for this group including children of children
2929 Returns all children groups for this group including children of children
2912 """
2930 """
2913 return self._recursive_objects(include_repos=False)
2931 return self._recursive_objects(include_repos=False)
2914
2932
2915 def recursive_repos(self):
2933 def recursive_repos(self):
2916 """
2934 """
2917 Returns all children repositories for this group
2935 Returns all children repositories for this group
2918 """
2936 """
2919 return self._recursive_objects(include_groups=False)
2937 return self._recursive_objects(include_groups=False)
2920
2938
2921 def get_new_name(self, group_name):
2939 def get_new_name(self, group_name):
2922 """
2940 """
2923 returns new full group name based on parent and new name
2941 returns new full group name based on parent and new name
2924
2942
2925 :param group_name:
2943 :param group_name:
2926 """
2944 """
2927 path_prefix = (self.parent_group.full_path_splitted if
2945 path_prefix = (self.parent_group.full_path_splitted if
2928 self.parent_group else [])
2946 self.parent_group else [])
2929 return RepoGroup.url_sep().join(path_prefix + [group_name])
2947 return RepoGroup.url_sep().join(path_prefix + [group_name])
2930
2948
2931 def update_commit_cache(self, config=None):
2949 def update_commit_cache(self, config=None):
2932 """
2950 """
2933 Update cache of last commit for newest repository inside this repository group.
2951 Update cache of last commit for newest repository inside this repository group.
2934 cache_keys should be::
2952 cache_keys should be::
2935
2953
2936 source_repo_id
2954 source_repo_id
2937 short_id
2955 short_id
2938 raw_id
2956 raw_id
2939 revision
2957 revision
2940 parents
2958 parents
2941 message
2959 message
2942 date
2960 date
2943 author
2961 author
2944
2962
2945 """
2963 """
2946 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2964 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2947 empty_date = datetime.datetime.fromtimestamp(0)
2965 empty_date = datetime.datetime.fromtimestamp(0)
2948
2966
2949 def repo_groups_and_repos(root_gr):
2967 def repo_groups_and_repos(root_gr):
2950 for _repo in root_gr.repositories:
2968 for _repo in root_gr.repositories:
2951 yield _repo
2969 yield _repo
2952 for child_group in root_gr.children.all():
2970 for child_group in root_gr.children.all():
2953 yield child_group
2971 yield child_group
2954
2972
2955 latest_repo_cs_cache = {}
2973 latest_repo_cs_cache = {}
2956 for obj in repo_groups_and_repos(self):
2974 for obj in repo_groups_and_repos(self):
2957 repo_cs_cache = obj.changeset_cache
2975 repo_cs_cache = obj.changeset_cache
2958 date_latest = latest_repo_cs_cache.get('date', empty_date)
2976 date_latest = latest_repo_cs_cache.get('date', empty_date)
2959 date_current = repo_cs_cache.get('date', empty_date)
2977 date_current = repo_cs_cache.get('date', empty_date)
2960 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2978 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2961 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2979 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2962 latest_repo_cs_cache = repo_cs_cache
2980 latest_repo_cs_cache = repo_cs_cache
2963 if hasattr(obj, 'repo_id'):
2981 if hasattr(obj, 'repo_id'):
2964 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2982 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2965 else:
2983 else:
2966 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2984 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2967
2985
2968 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2986 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2969
2987
2970 latest_repo_cs_cache['updated_on'] = time.time()
2988 latest_repo_cs_cache['updated_on'] = time.time()
2971 self.changeset_cache = latest_repo_cs_cache
2989 self.changeset_cache = latest_repo_cs_cache
2972 self.updated_on = _date_latest
2990 self.updated_on = _date_latest
2973 Session().add(self)
2991 Session().add(self)
2974 Session().commit()
2992 Session().commit()
2975
2993
2976 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2994 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2977 self.group_name, latest_repo_cs_cache, _date_latest)
2995 self.group_name, latest_repo_cs_cache, _date_latest)
2978
2996
2979 def permissions(self, with_admins=True, with_owner=True,
2997 def permissions(self, with_admins=True, with_owner=True,
2980 expand_from_user_groups=False):
2998 expand_from_user_groups=False):
2981 """
2999 """
2982 Permissions for repository groups
3000 Permissions for repository groups
2983 """
3001 """
2984 _admin_perm = 'group.admin'
3002 _admin_perm = 'group.admin'
2985
3003
2986 owner_row = []
3004 owner_row = []
2987 if with_owner:
3005 if with_owner:
2988 usr = AttributeDict(self.user.get_dict())
3006 usr = AttributeDict(self.user.get_dict())
2989 usr.owner_row = True
3007 usr.owner_row = True
2990 usr.permission = _admin_perm
3008 usr.permission = _admin_perm
2991 owner_row.append(usr)
3009 owner_row.append(usr)
2992
3010
2993 super_admin_ids = []
3011 super_admin_ids = []
2994 super_admin_rows = []
3012 super_admin_rows = []
2995 if with_admins:
3013 if with_admins:
2996 for usr in User.get_all_super_admins():
3014 for usr in User.get_all_super_admins():
2997 super_admin_ids.append(usr.user_id)
3015 super_admin_ids.append(usr.user_id)
2998 # if this admin is also owner, don't double the record
3016 # if this admin is also owner, don't double the record
2999 if usr.user_id == owner_row[0].user_id:
3017 if usr.user_id == owner_row[0].user_id:
3000 owner_row[0].admin_row = True
3018 owner_row[0].admin_row = True
3001 else:
3019 else:
3002 usr = AttributeDict(usr.get_dict())
3020 usr = AttributeDict(usr.get_dict())
3003 usr.admin_row = True
3021 usr.admin_row = True
3004 usr.permission = _admin_perm
3022 usr.permission = _admin_perm
3005 super_admin_rows.append(usr)
3023 super_admin_rows.append(usr)
3006
3024
3007 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3025 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3008 q = q.options(joinedload(UserRepoGroupToPerm.group),
3026 q = q.options(joinedload(UserRepoGroupToPerm.group),
3009 joinedload(UserRepoGroupToPerm.user),
3027 joinedload(UserRepoGroupToPerm.user),
3010 joinedload(UserRepoGroupToPerm.permission),)
3028 joinedload(UserRepoGroupToPerm.permission),)
3011
3029
3012 # get owners and admins and permissions. We do a trick of re-writing
3030 # get owners and admins and permissions. We do a trick of re-writing
3013 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3031 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3014 # has a global reference and changing one object propagates to all
3032 # has a global reference and changing one object propagates to all
3015 # others. This means if admin is also an owner admin_row that change
3033 # others. This means if admin is also an owner admin_row that change
3016 # would propagate to both objects
3034 # would propagate to both objects
3017 perm_rows = []
3035 perm_rows = []
3018 for _usr in q.all():
3036 for _usr in q.all():
3019 usr = AttributeDict(_usr.user.get_dict())
3037 usr = AttributeDict(_usr.user.get_dict())
3020 # if this user is also owner/admin, mark as duplicate record
3038 # if this user is also owner/admin, mark as duplicate record
3021 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3039 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3022 usr.duplicate_perm = True
3040 usr.duplicate_perm = True
3023 usr.permission = _usr.permission.permission_name
3041 usr.permission = _usr.permission.permission_name
3024 perm_rows.append(usr)
3042 perm_rows.append(usr)
3025
3043
3026 # filter the perm rows by 'default' first and then sort them by
3044 # filter the perm rows by 'default' first and then sort them by
3027 # admin,write,read,none permissions sorted again alphabetically in
3045 # admin,write,read,none permissions sorted again alphabetically in
3028 # each group
3046 # each group
3029 perm_rows = sorted(perm_rows, key=display_user_sort)
3047 perm_rows = sorted(perm_rows, key=display_user_sort)
3030
3048
3031 user_groups_rows = []
3049 user_groups_rows = []
3032 if expand_from_user_groups:
3050 if expand_from_user_groups:
3033 for ug in self.permission_user_groups(with_members=True):
3051 for ug in self.permission_user_groups(with_members=True):
3034 for user_data in ug.members:
3052 for user_data in ug.members:
3035 user_groups_rows.append(user_data)
3053 user_groups_rows.append(user_data)
3036
3054
3037 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3055 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3038
3056
3039 def permission_user_groups(self, with_members=False):
3057 def permission_user_groups(self, with_members=False):
3040 q = UserGroupRepoGroupToPerm.query()\
3058 q = UserGroupRepoGroupToPerm.query()\
3041 .filter(UserGroupRepoGroupToPerm.group == self)
3059 .filter(UserGroupRepoGroupToPerm.group == self)
3042 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3060 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3043 joinedload(UserGroupRepoGroupToPerm.users_group),
3061 joinedload(UserGroupRepoGroupToPerm.users_group),
3044 joinedload(UserGroupRepoGroupToPerm.permission),)
3062 joinedload(UserGroupRepoGroupToPerm.permission),)
3045
3063
3046 perm_rows = []
3064 perm_rows = []
3047 for _user_group in q.all():
3065 for _user_group in q.all():
3048 entry = AttributeDict(_user_group.users_group.get_dict())
3066 entry = AttributeDict(_user_group.users_group.get_dict())
3049 entry.permission = _user_group.permission.permission_name
3067 entry.permission = _user_group.permission.permission_name
3050 if with_members:
3068 if with_members:
3051 entry.members = [x.user.get_dict()
3069 entry.members = [x.user.get_dict()
3052 for x in _user_group.users_group.members]
3070 for x in _user_group.users_group.members]
3053 perm_rows.append(entry)
3071 perm_rows.append(entry)
3054
3072
3055 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3073 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3056 return perm_rows
3074 return perm_rows
3057
3075
3058 def get_api_data(self):
3076 def get_api_data(self):
3059 """
3077 """
3060 Common function for generating api data
3078 Common function for generating api data
3061
3079
3062 """
3080 """
3063 group = self
3081 group = self
3064 data = {
3082 data = {
3065 'group_id': group.group_id,
3083 'group_id': group.group_id,
3066 'group_name': group.group_name,
3084 'group_name': group.group_name,
3067 'group_description': group.description_safe,
3085 'group_description': group.description_safe,
3068 'parent_group': group.parent_group.group_name if group.parent_group else None,
3086 'parent_group': group.parent_group.group_name if group.parent_group else None,
3069 'repositories': [x.repo_name for x in group.repositories],
3087 'repositories': [x.repo_name for x in group.repositories],
3070 'owner': group.user.username,
3088 'owner': group.user.username,
3071 }
3089 }
3072 return data
3090 return data
3073
3091
3074 def get_dict(self):
3092 def get_dict(self):
3075 # Since we transformed `group_name` to a hybrid property, we need to
3093 # Since we transformed `group_name` to a hybrid property, we need to
3076 # keep compatibility with the code which uses `group_name` field.
3094 # keep compatibility with the code which uses `group_name` field.
3077 result = super(RepoGroup, self).get_dict()
3095 result = super(RepoGroup, self).get_dict()
3078 result['group_name'] = result.pop('_group_name', None)
3096 result['group_name'] = result.pop('_group_name', None)
3079 return result
3097 return result
3080
3098
3081
3099
3082 class Permission(Base, BaseModel):
3100 class Permission(Base, BaseModel):
3083 __tablename__ = 'permissions'
3101 __tablename__ = 'permissions'
3084 __table_args__ = (
3102 __table_args__ = (
3085 Index('p_perm_name_idx', 'permission_name'),
3103 Index('p_perm_name_idx', 'permission_name'),
3086 base_table_args,
3104 base_table_args,
3087 )
3105 )
3088
3106
3089 PERMS = [
3107 PERMS = [
3090 ('hg.admin', _('RhodeCode Super Administrator')),
3108 ('hg.admin', _('RhodeCode Super Administrator')),
3091
3109
3092 ('repository.none', _('Repository no access')),
3110 ('repository.none', _('Repository no access')),
3093 ('repository.read', _('Repository read access')),
3111 ('repository.read', _('Repository read access')),
3094 ('repository.write', _('Repository write access')),
3112 ('repository.write', _('Repository write access')),
3095 ('repository.admin', _('Repository admin access')),
3113 ('repository.admin', _('Repository admin access')),
3096
3114
3097 ('group.none', _('Repository group no access')),
3115 ('group.none', _('Repository group no access')),
3098 ('group.read', _('Repository group read access')),
3116 ('group.read', _('Repository group read access')),
3099 ('group.write', _('Repository group write access')),
3117 ('group.write', _('Repository group write access')),
3100 ('group.admin', _('Repository group admin access')),
3118 ('group.admin', _('Repository group admin access')),
3101
3119
3102 ('usergroup.none', _('User group no access')),
3120 ('usergroup.none', _('User group no access')),
3103 ('usergroup.read', _('User group read access')),
3121 ('usergroup.read', _('User group read access')),
3104 ('usergroup.write', _('User group write access')),
3122 ('usergroup.write', _('User group write access')),
3105 ('usergroup.admin', _('User group admin access')),
3123 ('usergroup.admin', _('User group admin access')),
3106
3124
3107 ('branch.none', _('Branch no permissions')),
3125 ('branch.none', _('Branch no permissions')),
3108 ('branch.merge', _('Branch access by web merge')),
3126 ('branch.merge', _('Branch access by web merge')),
3109 ('branch.push', _('Branch access by push')),
3127 ('branch.push', _('Branch access by push')),
3110 ('branch.push_force', _('Branch access by push with force')),
3128 ('branch.push_force', _('Branch access by push with force')),
3111
3129
3112 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3130 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3113 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3131 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3114
3132
3115 ('hg.usergroup.create.false', _('User Group creation disabled')),
3133 ('hg.usergroup.create.false', _('User Group creation disabled')),
3116 ('hg.usergroup.create.true', _('User Group creation enabled')),
3134 ('hg.usergroup.create.true', _('User Group creation enabled')),
3117
3135
3118 ('hg.create.none', _('Repository creation disabled')),
3136 ('hg.create.none', _('Repository creation disabled')),
3119 ('hg.create.repository', _('Repository creation enabled')),
3137 ('hg.create.repository', _('Repository creation enabled')),
3120 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3138 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3121 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3139 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3122
3140
3123 ('hg.fork.none', _('Repository forking disabled')),
3141 ('hg.fork.none', _('Repository forking disabled')),
3124 ('hg.fork.repository', _('Repository forking enabled')),
3142 ('hg.fork.repository', _('Repository forking enabled')),
3125
3143
3126 ('hg.register.none', _('Registration disabled')),
3144 ('hg.register.none', _('Registration disabled')),
3127 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3145 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3128 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3146 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3129
3147
3130 ('hg.password_reset.enabled', _('Password reset enabled')),
3148 ('hg.password_reset.enabled', _('Password reset enabled')),
3131 ('hg.password_reset.hidden', _('Password reset hidden')),
3149 ('hg.password_reset.hidden', _('Password reset hidden')),
3132 ('hg.password_reset.disabled', _('Password reset disabled')),
3150 ('hg.password_reset.disabled', _('Password reset disabled')),
3133
3151
3134 ('hg.extern_activate.manual', _('Manual activation of external account')),
3152 ('hg.extern_activate.manual', _('Manual activation of external account')),
3135 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3153 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3136
3154
3137 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3155 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3138 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3156 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3139 ]
3157 ]
3140
3158
3141 # definition of system default permissions for DEFAULT user, created on
3159 # definition of system default permissions for DEFAULT user, created on
3142 # system setup
3160 # system setup
3143 DEFAULT_USER_PERMISSIONS = [
3161 DEFAULT_USER_PERMISSIONS = [
3144 # object perms
3162 # object perms
3145 'repository.read',
3163 'repository.read',
3146 'group.read',
3164 'group.read',
3147 'usergroup.read',
3165 'usergroup.read',
3148 # branch, for backward compat we need same value as before so forced pushed
3166 # branch, for backward compat we need same value as before so forced pushed
3149 'branch.push_force',
3167 'branch.push_force',
3150 # global
3168 # global
3151 'hg.create.repository',
3169 'hg.create.repository',
3152 'hg.repogroup.create.false',
3170 'hg.repogroup.create.false',
3153 'hg.usergroup.create.false',
3171 'hg.usergroup.create.false',
3154 'hg.create.write_on_repogroup.true',
3172 'hg.create.write_on_repogroup.true',
3155 'hg.fork.repository',
3173 'hg.fork.repository',
3156 'hg.register.manual_activate',
3174 'hg.register.manual_activate',
3157 'hg.password_reset.enabled',
3175 'hg.password_reset.enabled',
3158 'hg.extern_activate.auto',
3176 'hg.extern_activate.auto',
3159 'hg.inherit_default_perms.true',
3177 'hg.inherit_default_perms.true',
3160 ]
3178 ]
3161
3179
3162 # defines which permissions are more important higher the more important
3180 # defines which permissions are more important higher the more important
3163 # Weight defines which permissions are more important.
3181 # Weight defines which permissions are more important.
3164 # The higher number the more important.
3182 # The higher number the more important.
3165 PERM_WEIGHTS = {
3183 PERM_WEIGHTS = {
3166 'repository.none': 0,
3184 'repository.none': 0,
3167 'repository.read': 1,
3185 'repository.read': 1,
3168 'repository.write': 3,
3186 'repository.write': 3,
3169 'repository.admin': 4,
3187 'repository.admin': 4,
3170
3188
3171 'group.none': 0,
3189 'group.none': 0,
3172 'group.read': 1,
3190 'group.read': 1,
3173 'group.write': 3,
3191 'group.write': 3,
3174 'group.admin': 4,
3192 'group.admin': 4,
3175
3193
3176 'usergroup.none': 0,
3194 'usergroup.none': 0,
3177 'usergroup.read': 1,
3195 'usergroup.read': 1,
3178 'usergroup.write': 3,
3196 'usergroup.write': 3,
3179 'usergroup.admin': 4,
3197 'usergroup.admin': 4,
3180
3198
3181 'branch.none': 0,
3199 'branch.none': 0,
3182 'branch.merge': 1,
3200 'branch.merge': 1,
3183 'branch.push': 3,
3201 'branch.push': 3,
3184 'branch.push_force': 4,
3202 'branch.push_force': 4,
3185
3203
3186 'hg.repogroup.create.false': 0,
3204 'hg.repogroup.create.false': 0,
3187 'hg.repogroup.create.true': 1,
3205 'hg.repogroup.create.true': 1,
3188
3206
3189 'hg.usergroup.create.false': 0,
3207 'hg.usergroup.create.false': 0,
3190 'hg.usergroup.create.true': 1,
3208 'hg.usergroup.create.true': 1,
3191
3209
3192 'hg.fork.none': 0,
3210 'hg.fork.none': 0,
3193 'hg.fork.repository': 1,
3211 'hg.fork.repository': 1,
3194 'hg.create.none': 0,
3212 'hg.create.none': 0,
3195 'hg.create.repository': 1
3213 'hg.create.repository': 1
3196 }
3214 }
3197
3215
3198 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3216 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3199 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3217 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3200 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3218 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3201
3219
3202 def __unicode__(self):
3220 def __unicode__(self):
3203 return u"<%s('%s:%s')>" % (
3221 return u"<%s('%s:%s')>" % (
3204 self.__class__.__name__, self.permission_id, self.permission_name
3222 self.__class__.__name__, self.permission_id, self.permission_name
3205 )
3223 )
3206
3224
3207 @classmethod
3225 @classmethod
3208 def get_by_key(cls, key):
3226 def get_by_key(cls, key):
3209 return cls.query().filter(cls.permission_name == key).scalar()
3227 return cls.query().filter(cls.permission_name == key).scalar()
3210
3228
3211 @classmethod
3229 @classmethod
3212 def get_default_repo_perms(cls, user_id, repo_id=None):
3230 def get_default_repo_perms(cls, user_id, repo_id=None):
3213 q = Session().query(UserRepoToPerm, Repository, Permission)\
3231 q = Session().query(UserRepoToPerm, Repository, Permission)\
3214 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3232 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3215 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3233 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3216 .filter(UserRepoToPerm.user_id == user_id)
3234 .filter(UserRepoToPerm.user_id == user_id)
3217 if repo_id:
3235 if repo_id:
3218 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3236 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3219 return q.all()
3237 return q.all()
3220
3238
3221 @classmethod
3239 @classmethod
3222 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3240 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3223 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3241 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3224 .join(
3242 .join(
3225 Permission,
3243 Permission,
3226 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3244 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3227 .join(
3245 .join(
3228 UserRepoToPerm,
3246 UserRepoToPerm,
3229 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3247 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3230 .filter(UserRepoToPerm.user_id == user_id)
3248 .filter(UserRepoToPerm.user_id == user_id)
3231
3249
3232 if repo_id:
3250 if repo_id:
3233 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3251 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3234 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3252 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3235
3253
3236 @classmethod
3254 @classmethod
3237 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3255 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3238 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3256 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3239 .join(
3257 .join(
3240 Permission,
3258 Permission,
3241 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3259 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3242 .join(
3260 .join(
3243 Repository,
3261 Repository,
3244 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3262 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3245 .join(
3263 .join(
3246 UserGroup,
3264 UserGroup,
3247 UserGroupRepoToPerm.users_group_id ==
3265 UserGroupRepoToPerm.users_group_id ==
3248 UserGroup.users_group_id)\
3266 UserGroup.users_group_id)\
3249 .join(
3267 .join(
3250 UserGroupMember,
3268 UserGroupMember,
3251 UserGroupRepoToPerm.users_group_id ==
3269 UserGroupRepoToPerm.users_group_id ==
3252 UserGroupMember.users_group_id)\
3270 UserGroupMember.users_group_id)\
3253 .filter(
3271 .filter(
3254 UserGroupMember.user_id == user_id,
3272 UserGroupMember.user_id == user_id,
3255 UserGroup.users_group_active == true())
3273 UserGroup.users_group_active == true())
3256 if repo_id:
3274 if repo_id:
3257 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3275 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3258 return q.all()
3276 return q.all()
3259
3277
3260 @classmethod
3278 @classmethod
3261 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3279 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3262 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3280 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3263 .join(
3281 .join(
3264 Permission,
3282 Permission,
3265 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3283 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3266 .join(
3284 .join(
3267 UserGroupRepoToPerm,
3285 UserGroupRepoToPerm,
3268 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3286 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3269 .join(
3287 .join(
3270 UserGroup,
3288 UserGroup,
3271 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3289 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3272 .join(
3290 .join(
3273 UserGroupMember,
3291 UserGroupMember,
3274 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3292 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3275 .filter(
3293 .filter(
3276 UserGroupMember.user_id == user_id,
3294 UserGroupMember.user_id == user_id,
3277 UserGroup.users_group_active == true())
3295 UserGroup.users_group_active == true())
3278
3296
3279 if repo_id:
3297 if repo_id:
3280 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3298 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3281 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3299 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3282
3300
3283 @classmethod
3301 @classmethod
3284 def get_default_group_perms(cls, user_id, repo_group_id=None):
3302 def get_default_group_perms(cls, user_id, repo_group_id=None):
3285 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3303 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3286 .join(
3304 .join(
3287 Permission,
3305 Permission,
3288 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3306 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3289 .join(
3307 .join(
3290 RepoGroup,
3308 RepoGroup,
3291 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3309 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3292 .filter(UserRepoGroupToPerm.user_id == user_id)
3310 .filter(UserRepoGroupToPerm.user_id == user_id)
3293 if repo_group_id:
3311 if repo_group_id:
3294 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3312 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3295 return q.all()
3313 return q.all()
3296
3314
3297 @classmethod
3315 @classmethod
3298 def get_default_group_perms_from_user_group(
3316 def get_default_group_perms_from_user_group(
3299 cls, user_id, repo_group_id=None):
3317 cls, user_id, repo_group_id=None):
3300 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3318 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3301 .join(
3319 .join(
3302 Permission,
3320 Permission,
3303 UserGroupRepoGroupToPerm.permission_id ==
3321 UserGroupRepoGroupToPerm.permission_id ==
3304 Permission.permission_id)\
3322 Permission.permission_id)\
3305 .join(
3323 .join(
3306 RepoGroup,
3324 RepoGroup,
3307 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3325 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3308 .join(
3326 .join(
3309 UserGroup,
3327 UserGroup,
3310 UserGroupRepoGroupToPerm.users_group_id ==
3328 UserGroupRepoGroupToPerm.users_group_id ==
3311 UserGroup.users_group_id)\
3329 UserGroup.users_group_id)\
3312 .join(
3330 .join(
3313 UserGroupMember,
3331 UserGroupMember,
3314 UserGroupRepoGroupToPerm.users_group_id ==
3332 UserGroupRepoGroupToPerm.users_group_id ==
3315 UserGroupMember.users_group_id)\
3333 UserGroupMember.users_group_id)\
3316 .filter(
3334 .filter(
3317 UserGroupMember.user_id == user_id,
3335 UserGroupMember.user_id == user_id,
3318 UserGroup.users_group_active == true())
3336 UserGroup.users_group_active == true())
3319 if repo_group_id:
3337 if repo_group_id:
3320 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3338 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3321 return q.all()
3339 return q.all()
3322
3340
3323 @classmethod
3341 @classmethod
3324 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3342 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3325 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3343 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3326 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3344 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3327 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3345 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3328 .filter(UserUserGroupToPerm.user_id == user_id)
3346 .filter(UserUserGroupToPerm.user_id == user_id)
3329 if user_group_id:
3347 if user_group_id:
3330 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3348 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3331 return q.all()
3349 return q.all()
3332
3350
3333 @classmethod
3351 @classmethod
3334 def get_default_user_group_perms_from_user_group(
3352 def get_default_user_group_perms_from_user_group(
3335 cls, user_id, user_group_id=None):
3353 cls, user_id, user_group_id=None):
3336 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3354 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3337 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3355 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3338 .join(
3356 .join(
3339 Permission,
3357 Permission,
3340 UserGroupUserGroupToPerm.permission_id ==
3358 UserGroupUserGroupToPerm.permission_id ==
3341 Permission.permission_id)\
3359 Permission.permission_id)\
3342 .join(
3360 .join(
3343 TargetUserGroup,
3361 TargetUserGroup,
3344 UserGroupUserGroupToPerm.target_user_group_id ==
3362 UserGroupUserGroupToPerm.target_user_group_id ==
3345 TargetUserGroup.users_group_id)\
3363 TargetUserGroup.users_group_id)\
3346 .join(
3364 .join(
3347 UserGroup,
3365 UserGroup,
3348 UserGroupUserGroupToPerm.user_group_id ==
3366 UserGroupUserGroupToPerm.user_group_id ==
3349 UserGroup.users_group_id)\
3367 UserGroup.users_group_id)\
3350 .join(
3368 .join(
3351 UserGroupMember,
3369 UserGroupMember,
3352 UserGroupUserGroupToPerm.user_group_id ==
3370 UserGroupUserGroupToPerm.user_group_id ==
3353 UserGroupMember.users_group_id)\
3371 UserGroupMember.users_group_id)\
3354 .filter(
3372 .filter(
3355 UserGroupMember.user_id == user_id,
3373 UserGroupMember.user_id == user_id,
3356 UserGroup.users_group_active == true())
3374 UserGroup.users_group_active == true())
3357 if user_group_id:
3375 if user_group_id:
3358 q = q.filter(
3376 q = q.filter(
3359 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3377 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3360
3378
3361 return q.all()
3379 return q.all()
3362
3380
3363
3381
3364 class UserRepoToPerm(Base, BaseModel):
3382 class UserRepoToPerm(Base, BaseModel):
3365 __tablename__ = 'repo_to_perm'
3383 __tablename__ = 'repo_to_perm'
3366 __table_args__ = (
3384 __table_args__ = (
3367 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3385 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3368 base_table_args
3386 base_table_args
3369 )
3387 )
3370
3388
3371 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3389 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3372 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3390 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3373 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3391 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3374 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3392 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3375
3393
3376 user = relationship('User')
3394 user = relationship('User')
3377 repository = relationship('Repository')
3395 repository = relationship('Repository')
3378 permission = relationship('Permission')
3396 permission = relationship('Permission')
3379
3397
3380 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3398 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3381
3399
3382 @classmethod
3400 @classmethod
3383 def create(cls, user, repository, permission):
3401 def create(cls, user, repository, permission):
3384 n = cls()
3402 n = cls()
3385 n.user = user
3403 n.user = user
3386 n.repository = repository
3404 n.repository = repository
3387 n.permission = permission
3405 n.permission = permission
3388 Session().add(n)
3406 Session().add(n)
3389 return n
3407 return n
3390
3408
3391 def __unicode__(self):
3409 def __unicode__(self):
3392 return u'<%s => %s >' % (self.user, self.repository)
3410 return u'<%s => %s >' % (self.user, self.repository)
3393
3411
3394
3412
3395 class UserUserGroupToPerm(Base, BaseModel):
3413 class UserUserGroupToPerm(Base, BaseModel):
3396 __tablename__ = 'user_user_group_to_perm'
3414 __tablename__ = 'user_user_group_to_perm'
3397 __table_args__ = (
3415 __table_args__ = (
3398 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3416 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3399 base_table_args
3417 base_table_args
3400 )
3418 )
3401
3419
3402 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3420 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3403 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3421 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3422 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3405 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3423 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3406
3424
3407 user = relationship('User')
3425 user = relationship('User')
3408 user_group = relationship('UserGroup')
3426 user_group = relationship('UserGroup')
3409 permission = relationship('Permission')
3427 permission = relationship('Permission')
3410
3428
3411 @classmethod
3429 @classmethod
3412 def create(cls, user, user_group, permission):
3430 def create(cls, user, user_group, permission):
3413 n = cls()
3431 n = cls()
3414 n.user = user
3432 n.user = user
3415 n.user_group = user_group
3433 n.user_group = user_group
3416 n.permission = permission
3434 n.permission = permission
3417 Session().add(n)
3435 Session().add(n)
3418 return n
3436 return n
3419
3437
3420 def __unicode__(self):
3438 def __unicode__(self):
3421 return u'<%s => %s >' % (self.user, self.user_group)
3439 return u'<%s => %s >' % (self.user, self.user_group)
3422
3440
3423
3441
3424 class UserToPerm(Base, BaseModel):
3442 class UserToPerm(Base, BaseModel):
3425 __tablename__ = 'user_to_perm'
3443 __tablename__ = 'user_to_perm'
3426 __table_args__ = (
3444 __table_args__ = (
3427 UniqueConstraint('user_id', 'permission_id'),
3445 UniqueConstraint('user_id', 'permission_id'),
3428 base_table_args
3446 base_table_args
3429 )
3447 )
3430
3448
3431 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3449 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3432 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3450 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3433 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3451 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3434
3452
3435 user = relationship('User')
3453 user = relationship('User')
3436 permission = relationship('Permission', lazy='joined')
3454 permission = relationship('Permission', lazy='joined')
3437
3455
3438 def __unicode__(self):
3456 def __unicode__(self):
3439 return u'<%s => %s >' % (self.user, self.permission)
3457 return u'<%s => %s >' % (self.user, self.permission)
3440
3458
3441
3459
3442 class UserGroupRepoToPerm(Base, BaseModel):
3460 class UserGroupRepoToPerm(Base, BaseModel):
3443 __tablename__ = 'users_group_repo_to_perm'
3461 __tablename__ = 'users_group_repo_to_perm'
3444 __table_args__ = (
3462 __table_args__ = (
3445 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3463 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3446 base_table_args
3464 base_table_args
3447 )
3465 )
3448
3466
3449 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3467 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3450 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3468 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3451 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3469 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3452 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3470 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3453
3471
3454 users_group = relationship('UserGroup')
3472 users_group = relationship('UserGroup')
3455 permission = relationship('Permission')
3473 permission = relationship('Permission')
3456 repository = relationship('Repository')
3474 repository = relationship('Repository')
3457 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3475 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3458
3476
3459 @classmethod
3477 @classmethod
3460 def create(cls, users_group, repository, permission):
3478 def create(cls, users_group, repository, permission):
3461 n = cls()
3479 n = cls()
3462 n.users_group = users_group
3480 n.users_group = users_group
3463 n.repository = repository
3481 n.repository = repository
3464 n.permission = permission
3482 n.permission = permission
3465 Session().add(n)
3483 Session().add(n)
3466 return n
3484 return n
3467
3485
3468 def __unicode__(self):
3486 def __unicode__(self):
3469 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3487 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3470
3488
3471
3489
3472 class UserGroupUserGroupToPerm(Base, BaseModel):
3490 class UserGroupUserGroupToPerm(Base, BaseModel):
3473 __tablename__ = 'user_group_user_group_to_perm'
3491 __tablename__ = 'user_group_user_group_to_perm'
3474 __table_args__ = (
3492 __table_args__ = (
3475 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3493 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3476 CheckConstraint('target_user_group_id != user_group_id'),
3494 CheckConstraint('target_user_group_id != user_group_id'),
3477 base_table_args
3495 base_table_args
3478 )
3496 )
3479
3497
3480 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)
3498 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)
3481 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3499 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3482 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3500 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3483 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3501 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3484
3502
3485 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3503 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3486 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3504 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3487 permission = relationship('Permission')
3505 permission = relationship('Permission')
3488
3506
3489 @classmethod
3507 @classmethod
3490 def create(cls, target_user_group, user_group, permission):
3508 def create(cls, target_user_group, user_group, permission):
3491 n = cls()
3509 n = cls()
3492 n.target_user_group = target_user_group
3510 n.target_user_group = target_user_group
3493 n.user_group = user_group
3511 n.user_group = user_group
3494 n.permission = permission
3512 n.permission = permission
3495 Session().add(n)
3513 Session().add(n)
3496 return n
3514 return n
3497
3515
3498 def __unicode__(self):
3516 def __unicode__(self):
3499 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3517 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3500
3518
3501
3519
3502 class UserGroupToPerm(Base, BaseModel):
3520 class UserGroupToPerm(Base, BaseModel):
3503 __tablename__ = 'users_group_to_perm'
3521 __tablename__ = 'users_group_to_perm'
3504 __table_args__ = (
3522 __table_args__ = (
3505 UniqueConstraint('users_group_id', 'permission_id',),
3523 UniqueConstraint('users_group_id', 'permission_id',),
3506 base_table_args
3524 base_table_args
3507 )
3525 )
3508
3526
3509 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3527 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3510 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3528 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3511 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3529 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3512
3530
3513 users_group = relationship('UserGroup')
3531 users_group = relationship('UserGroup')
3514 permission = relationship('Permission')
3532 permission = relationship('Permission')
3515
3533
3516
3534
3517 class UserRepoGroupToPerm(Base, BaseModel):
3535 class UserRepoGroupToPerm(Base, BaseModel):
3518 __tablename__ = 'user_repo_group_to_perm'
3536 __tablename__ = 'user_repo_group_to_perm'
3519 __table_args__ = (
3537 __table_args__ = (
3520 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3538 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3521 base_table_args
3539 base_table_args
3522 )
3540 )
3523
3541
3524 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3542 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3525 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3543 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3526 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3544 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3527 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3545 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3528
3546
3529 user = relationship('User')
3547 user = relationship('User')
3530 group = relationship('RepoGroup')
3548 group = relationship('RepoGroup')
3531 permission = relationship('Permission')
3549 permission = relationship('Permission')
3532
3550
3533 @classmethod
3551 @classmethod
3534 def create(cls, user, repository_group, permission):
3552 def create(cls, user, repository_group, permission):
3535 n = cls()
3553 n = cls()
3536 n.user = user
3554 n.user = user
3537 n.group = repository_group
3555 n.group = repository_group
3538 n.permission = permission
3556 n.permission = permission
3539 Session().add(n)
3557 Session().add(n)
3540 return n
3558 return n
3541
3559
3542
3560
3543 class UserGroupRepoGroupToPerm(Base, BaseModel):
3561 class UserGroupRepoGroupToPerm(Base, BaseModel):
3544 __tablename__ = 'users_group_repo_group_to_perm'
3562 __tablename__ = 'users_group_repo_group_to_perm'
3545 __table_args__ = (
3563 __table_args__ = (
3546 UniqueConstraint('users_group_id', 'group_id'),
3564 UniqueConstraint('users_group_id', 'group_id'),
3547 base_table_args
3565 base_table_args
3548 )
3566 )
3549
3567
3550 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)
3568 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)
3551 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3569 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3552 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3570 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3553 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3571 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3554
3572
3555 users_group = relationship('UserGroup')
3573 users_group = relationship('UserGroup')
3556 permission = relationship('Permission')
3574 permission = relationship('Permission')
3557 group = relationship('RepoGroup')
3575 group = relationship('RepoGroup')
3558
3576
3559 @classmethod
3577 @classmethod
3560 def create(cls, user_group, repository_group, permission):
3578 def create(cls, user_group, repository_group, permission):
3561 n = cls()
3579 n = cls()
3562 n.users_group = user_group
3580 n.users_group = user_group
3563 n.group = repository_group
3581 n.group = repository_group
3564 n.permission = permission
3582 n.permission = permission
3565 Session().add(n)
3583 Session().add(n)
3566 return n
3584 return n
3567
3585
3568 def __unicode__(self):
3586 def __unicode__(self):
3569 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3587 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3570
3588
3571
3589
3572 class Statistics(Base, BaseModel):
3590 class Statistics(Base, BaseModel):
3573 __tablename__ = 'statistics'
3591 __tablename__ = 'statistics'
3574 __table_args__ = (
3592 __table_args__ = (
3575 base_table_args
3593 base_table_args
3576 )
3594 )
3577
3595
3578 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3596 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3579 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3597 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3580 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3598 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3581 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3599 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3582 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3600 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3583 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3601 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3584
3602
3585 repository = relationship('Repository', single_parent=True)
3603 repository = relationship('Repository', single_parent=True)
3586
3604
3587
3605
3588 class UserFollowing(Base, BaseModel):
3606 class UserFollowing(Base, BaseModel):
3589 __tablename__ = 'user_followings'
3607 __tablename__ = 'user_followings'
3590 __table_args__ = (
3608 __table_args__ = (
3591 UniqueConstraint('user_id', 'follows_repository_id'),
3609 UniqueConstraint('user_id', 'follows_repository_id'),
3592 UniqueConstraint('user_id', 'follows_user_id'),
3610 UniqueConstraint('user_id', 'follows_user_id'),
3593 base_table_args
3611 base_table_args
3594 )
3612 )
3595
3613
3596 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3614 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3615 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3598 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3616 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3599 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3617 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3600 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3618 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3601
3619
3602 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3620 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3603
3621
3604 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3622 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3605 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3623 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3606
3624
3607 @classmethod
3625 @classmethod
3608 def get_repo_followers(cls, repo_id):
3626 def get_repo_followers(cls, repo_id):
3609 return cls.query().filter(cls.follows_repo_id == repo_id)
3627 return cls.query().filter(cls.follows_repo_id == repo_id)
3610
3628
3611
3629
3612 class CacheKey(Base, BaseModel):
3630 class CacheKey(Base, BaseModel):
3613 __tablename__ = 'cache_invalidation'
3631 __tablename__ = 'cache_invalidation'
3614 __table_args__ = (
3632 __table_args__ = (
3615 UniqueConstraint('cache_key'),
3633 UniqueConstraint('cache_key'),
3616 Index('key_idx', 'cache_key'),
3634 Index('key_idx', 'cache_key'),
3617 base_table_args,
3635 base_table_args,
3618 )
3636 )
3619
3637
3620 CACHE_TYPE_FEED = 'FEED'
3638 CACHE_TYPE_FEED = 'FEED'
3621
3639
3622 # namespaces used to register process/thread aware caches
3640 # namespaces used to register process/thread aware caches
3623 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3641 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3624 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3642 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3625
3643
3626 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3644 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3627 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3645 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3628 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3646 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3629 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3647 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3630 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3648 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3631
3649
3632 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3650 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3633 self.cache_key = cache_key
3651 self.cache_key = cache_key
3634 self.cache_args = cache_args
3652 self.cache_args = cache_args
3635 self.cache_active = False
3653 self.cache_active = False
3636 # first key should be same for all entries, since all workers should share it
3654 # first key should be same for all entries, since all workers should share it
3637 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3655 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3638
3656
3639 def __unicode__(self):
3657 def __unicode__(self):
3640 return u"<%s('%s:%s[%s]')>" % (
3658 return u"<%s('%s:%s[%s]')>" % (
3641 self.__class__.__name__,
3659 self.__class__.__name__,
3642 self.cache_id, self.cache_key, self.cache_active)
3660 self.cache_id, self.cache_key, self.cache_active)
3643
3661
3644 def _cache_key_partition(self):
3662 def _cache_key_partition(self):
3645 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3663 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3646 return prefix, repo_name, suffix
3664 return prefix, repo_name, suffix
3647
3665
3648 def get_prefix(self):
3666 def get_prefix(self):
3649 """
3667 """
3650 Try to extract prefix from existing cache key. The key could consist
3668 Try to extract prefix from existing cache key. The key could consist
3651 of prefix, repo_name, suffix
3669 of prefix, repo_name, suffix
3652 """
3670 """
3653 # this returns prefix, repo_name, suffix
3671 # this returns prefix, repo_name, suffix
3654 return self._cache_key_partition()[0]
3672 return self._cache_key_partition()[0]
3655
3673
3656 def get_suffix(self):
3674 def get_suffix(self):
3657 """
3675 """
3658 get suffix that might have been used in _get_cache_key to
3676 get suffix that might have been used in _get_cache_key to
3659 generate self.cache_key. Only used for informational purposes
3677 generate self.cache_key. Only used for informational purposes
3660 in repo_edit.mako.
3678 in repo_edit.mako.
3661 """
3679 """
3662 # prefix, repo_name, suffix
3680 # prefix, repo_name, suffix
3663 return self._cache_key_partition()[2]
3681 return self._cache_key_partition()[2]
3664
3682
3665 @classmethod
3683 @classmethod
3666 def generate_new_state_uid(cls, based_on=None):
3684 def generate_new_state_uid(cls, based_on=None):
3667 if based_on:
3685 if based_on:
3668 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3686 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3669 else:
3687 else:
3670 return str(uuid.uuid4())
3688 return str(uuid.uuid4())
3671
3689
3672 @classmethod
3690 @classmethod
3673 def delete_all_cache(cls):
3691 def delete_all_cache(cls):
3674 """
3692 """
3675 Delete all cache keys from database.
3693 Delete all cache keys from database.
3676 Should only be run when all instances are down and all entries
3694 Should only be run when all instances are down and all entries
3677 thus stale.
3695 thus stale.
3678 """
3696 """
3679 cls.query().delete()
3697 cls.query().delete()
3680 Session().commit()
3698 Session().commit()
3681
3699
3682 @classmethod
3700 @classmethod
3683 def set_invalidate(cls, cache_uid, delete=False):
3701 def set_invalidate(cls, cache_uid, delete=False):
3684 """
3702 """
3685 Mark all caches of a repo as invalid in the database.
3703 Mark all caches of a repo as invalid in the database.
3686 """
3704 """
3687
3705
3688 try:
3706 try:
3689 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3707 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3690 if delete:
3708 if delete:
3691 qry.delete()
3709 qry.delete()
3692 log.debug('cache objects deleted for cache args %s',
3710 log.debug('cache objects deleted for cache args %s',
3693 safe_str(cache_uid))
3711 safe_str(cache_uid))
3694 else:
3712 else:
3695 qry.update({"cache_active": False,
3713 qry.update({"cache_active": False,
3696 "cache_state_uid": cls.generate_new_state_uid()})
3714 "cache_state_uid": cls.generate_new_state_uid()})
3697 log.debug('cache objects marked as invalid for cache args %s',
3715 log.debug('cache objects marked as invalid for cache args %s',
3698 safe_str(cache_uid))
3716 safe_str(cache_uid))
3699
3717
3700 Session().commit()
3718 Session().commit()
3701 except Exception:
3719 except Exception:
3702 log.exception(
3720 log.exception(
3703 'Cache key invalidation failed for cache args %s',
3721 'Cache key invalidation failed for cache args %s',
3704 safe_str(cache_uid))
3722 safe_str(cache_uid))
3705 Session().rollback()
3723 Session().rollback()
3706
3724
3707 @classmethod
3725 @classmethod
3708 def get_active_cache(cls, cache_key):
3726 def get_active_cache(cls, cache_key):
3709 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3727 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3710 if inv_obj:
3728 if inv_obj:
3711 return inv_obj
3729 return inv_obj
3712 return None
3730 return None
3713
3731
3714 @classmethod
3732 @classmethod
3715 def get_namespace_map(cls, namespace):
3733 def get_namespace_map(cls, namespace):
3716 return {
3734 return {
3717 x.cache_key: x
3735 x.cache_key: x
3718 for x in cls.query().filter(cls.cache_args == namespace)}
3736 for x in cls.query().filter(cls.cache_args == namespace)}
3719
3737
3720
3738
3721 class ChangesetComment(Base, BaseModel):
3739 class ChangesetComment(Base, BaseModel):
3722 __tablename__ = 'changeset_comments'
3740 __tablename__ = 'changeset_comments'
3723 __table_args__ = (
3741 __table_args__ = (
3724 Index('cc_revision_idx', 'revision'),
3742 Index('cc_revision_idx', 'revision'),
3725 base_table_args,
3743 base_table_args,
3726 )
3744 )
3727
3745
3728 COMMENT_OUTDATED = u'comment_outdated'
3746 COMMENT_OUTDATED = u'comment_outdated'
3729 COMMENT_TYPE_NOTE = u'note'
3747 COMMENT_TYPE_NOTE = u'note'
3730 COMMENT_TYPE_TODO = u'todo'
3748 COMMENT_TYPE_TODO = u'todo'
3731 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3749 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3732
3750
3733 OP_IMMUTABLE = u'immutable'
3751 OP_IMMUTABLE = u'immutable'
3734 OP_CHANGEABLE = u'changeable'
3752 OP_CHANGEABLE = u'changeable'
3735
3753
3736 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3754 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3737 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3755 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3738 revision = Column('revision', String(40), nullable=True)
3756 revision = Column('revision', String(40), nullable=True)
3739 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3757 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3740 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3758 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3741 line_no = Column('line_no', Unicode(10), nullable=True)
3759 line_no = Column('line_no', Unicode(10), nullable=True)
3742 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3760 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3743 f_path = Column('f_path', Unicode(1000), nullable=True)
3761 f_path = Column('f_path', Unicode(1000), nullable=True)
3744 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3762 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3745 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3763 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3746 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3764 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3747 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3765 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3748 renderer = Column('renderer', Unicode(64), nullable=True)
3766 renderer = Column('renderer', Unicode(64), nullable=True)
3749 display_state = Column('display_state', Unicode(128), nullable=True)
3767 display_state = Column('display_state', Unicode(128), nullable=True)
3750 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3768 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3751
3769
3752 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3770 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3753 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3771 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3754
3772
3755 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3773 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3756 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3774 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3757
3775
3758 author = relationship('User', lazy='joined')
3776 author = relationship('User', lazy='joined')
3759 repo = relationship('Repository')
3777 repo = relationship('Repository')
3760 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3778 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3761 pull_request = relationship('PullRequest', lazy='joined')
3779 pull_request = relationship('PullRequest', lazy='joined')
3762 pull_request_version = relationship('PullRequestVersion')
3780 pull_request_version = relationship('PullRequestVersion')
3763 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='joined', order_by='ChangesetCommentHistory.version')
3781 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='joined', order_by='ChangesetCommentHistory.version')
3764
3782
3765 @classmethod
3783 @classmethod
3766 def get_users(cls, revision=None, pull_request_id=None):
3784 def get_users(cls, revision=None, pull_request_id=None):
3767 """
3785 """
3768 Returns user associated with this ChangesetComment. ie those
3786 Returns user associated with this ChangesetComment. ie those
3769 who actually commented
3787 who actually commented
3770
3788
3771 :param cls:
3789 :param cls:
3772 :param revision:
3790 :param revision:
3773 """
3791 """
3774 q = Session().query(User)\
3792 q = Session().query(User)\
3775 .join(ChangesetComment.author)
3793 .join(ChangesetComment.author)
3776 if revision:
3794 if revision:
3777 q = q.filter(cls.revision == revision)
3795 q = q.filter(cls.revision == revision)
3778 elif pull_request_id:
3796 elif pull_request_id:
3779 q = q.filter(cls.pull_request_id == pull_request_id)
3797 q = q.filter(cls.pull_request_id == pull_request_id)
3780 return q.all()
3798 return q.all()
3781
3799
3782 @classmethod
3800 @classmethod
3783 def get_index_from_version(cls, pr_version, versions):
3801 def get_index_from_version(cls, pr_version, versions):
3784 num_versions = [x.pull_request_version_id for x in versions]
3802 num_versions = [x.pull_request_version_id for x in versions]
3785 try:
3803 try:
3786 return num_versions.index(pr_version) +1
3804 return num_versions.index(pr_version) + 1
3787 except (IndexError, ValueError):
3805 except (IndexError, ValueError):
3788 return
3806 return
3789
3807
3790 @property
3808 @property
3791 def outdated(self):
3809 def outdated(self):
3792 return self.display_state == self.COMMENT_OUTDATED
3810 return self.display_state == self.COMMENT_OUTDATED
3793
3811
3794 @property
3812 @property
3795 def immutable(self):
3813 def immutable(self):
3796 return self.immutable_state == self.OP_IMMUTABLE
3814 return self.immutable_state == self.OP_IMMUTABLE
3797
3815
3798 def outdated_at_version(self, version):
3816 def outdated_at_version(self, version):
3799 """
3817 """
3800 Checks if comment is outdated for given pull request version
3818 Checks if comment is outdated for given pull request version
3801 """
3819 """
3802 return self.outdated and self.pull_request_version_id != version
3820 return self.outdated and self.pull_request_version_id != version
3803
3821
3804 def older_than_version(self, version):
3822 def older_than_version(self, version):
3805 """
3823 """
3806 Checks if comment is made from previous version than given
3824 Checks if comment is made from previous version than given
3807 """
3825 """
3808 if version is None:
3826 if version is None:
3809 return self.pull_request_version_id is not None
3827 return self.pull_request_version_id is not None
3810
3828
3811 return self.pull_request_version_id < version
3829 return self.pull_request_version_id < version
3812
3830
3813 @property
3831 @property
3814 def commit_id(self):
3832 def commit_id(self):
3815 """New style naming to stop using .revision"""
3833 """New style naming to stop using .revision"""
3816 return self.revision
3834 return self.revision
3817
3835
3818 @property
3836 @property
3819 def resolved(self):
3837 def resolved(self):
3820 return self.resolved_by[0] if self.resolved_by else None
3838 return self.resolved_by[0] if self.resolved_by else None
3821
3839
3822 @property
3840 @property
3823 def is_todo(self):
3841 def is_todo(self):
3824 return self.comment_type == self.COMMENT_TYPE_TODO
3842 return self.comment_type == self.COMMENT_TYPE_TODO
3825
3843
3826 @property
3844 @property
3827 def is_inline(self):
3845 def is_inline(self):
3828 return self.line_no and self.f_path
3846 return self.line_no and self.f_path
3829
3847
3830 def get_index_version(self, versions):
3848 def get_index_version(self, versions):
3831 return self.get_index_from_version(
3849 return self.get_index_from_version(
3832 self.pull_request_version_id, versions)
3850 self.pull_request_version_id, versions)
3833
3851
3834 def __repr__(self):
3852 def __repr__(self):
3835 if self.comment_id:
3853 if self.comment_id:
3836 return '<DB:Comment #%s>' % self.comment_id
3854 return '<DB:Comment #%s>' % self.comment_id
3837 else:
3855 else:
3838 return '<DB:Comment at %#x>' % id(self)
3856 return '<DB:Comment at %#x>' % id(self)
3839
3857
3840 def get_api_data(self):
3858 def get_api_data(self):
3841 comment = self
3859 comment = self
3842 data = {
3860 data = {
3843 'comment_id': comment.comment_id,
3861 'comment_id': comment.comment_id,
3844 'comment_type': comment.comment_type,
3862 'comment_type': comment.comment_type,
3845 'comment_text': comment.text,
3863 'comment_text': comment.text,
3846 'comment_status': comment.status_change,
3864 'comment_status': comment.status_change,
3847 'comment_f_path': comment.f_path,
3865 'comment_f_path': comment.f_path,
3848 'comment_lineno': comment.line_no,
3866 'comment_lineno': comment.line_no,
3849 'comment_author': comment.author,
3867 'comment_author': comment.author,
3850 'comment_created_on': comment.created_on,
3868 'comment_created_on': comment.created_on,
3851 'comment_resolved_by': self.resolved,
3869 'comment_resolved_by': self.resolved,
3852 'comment_commit_id': comment.revision,
3870 'comment_commit_id': comment.revision,
3853 'comment_pull_request_id': comment.pull_request_id,
3871 'comment_pull_request_id': comment.pull_request_id,
3854 }
3872 }
3855 return data
3873 return data
3856
3874
3857 def __json__(self):
3875 def __json__(self):
3858 data = dict()
3876 data = dict()
3859 data.update(self.get_api_data())
3877 data.update(self.get_api_data())
3860 return data
3878 return data
3861
3879
3862
3880
3863 class ChangesetCommentHistory(Base, BaseModel):
3881 class ChangesetCommentHistory(Base, BaseModel):
3864 __tablename__ = 'changeset_comments_history'
3882 __tablename__ = 'changeset_comments_history'
3865 __table_args__ = (
3883 __table_args__ = (
3866 Index('cch_comment_id_idx', 'comment_id'),
3884 Index('cch_comment_id_idx', 'comment_id'),
3867 base_table_args,
3885 base_table_args,
3868 )
3886 )
3869
3887
3870 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3888 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3871 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3889 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3872 version = Column("version", Integer(), nullable=False, default=0)
3890 version = Column("version", Integer(), nullable=False, default=0)
3873 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3891 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3874 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3892 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3875 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3893 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3876 deleted = Column('deleted', Boolean(), default=False)
3894 deleted = Column('deleted', Boolean(), default=False)
3877
3895
3878 author = relationship('User', lazy='joined')
3896 author = relationship('User', lazy='joined')
3879 comment = relationship('ChangesetComment', cascade="all, delete")
3897 comment = relationship('ChangesetComment', cascade="all, delete")
3880
3898
3881 @classmethod
3899 @classmethod
3882 def get_version(cls, comment_id):
3900 def get_version(cls, comment_id):
3883 q = Session().query(ChangesetCommentHistory).filter(
3901 q = Session().query(ChangesetCommentHistory).filter(
3884 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3902 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3885 if q.count() == 0:
3903 if q.count() == 0:
3886 return 1
3904 return 1
3887 elif q.count() >= q[0].version:
3905 elif q.count() >= q[0].version:
3888 return q.count() + 1
3906 return q.count() + 1
3889 else:
3907 else:
3890 return q[0].version + 1
3908 return q[0].version + 1
3891
3909
3892
3910
3893 class ChangesetStatus(Base, BaseModel):
3911 class ChangesetStatus(Base, BaseModel):
3894 __tablename__ = 'changeset_statuses'
3912 __tablename__ = 'changeset_statuses'
3895 __table_args__ = (
3913 __table_args__ = (
3896 Index('cs_revision_idx', 'revision'),
3914 Index('cs_revision_idx', 'revision'),
3897 Index('cs_version_idx', 'version'),
3915 Index('cs_version_idx', 'version'),
3898 UniqueConstraint('repo_id', 'revision', 'version'),
3916 UniqueConstraint('repo_id', 'revision', 'version'),
3899 base_table_args
3917 base_table_args
3900 )
3918 )
3901
3919
3902 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3920 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3903 STATUS_APPROVED = 'approved'
3921 STATUS_APPROVED = 'approved'
3904 STATUS_REJECTED = 'rejected'
3922 STATUS_REJECTED = 'rejected'
3905 STATUS_UNDER_REVIEW = 'under_review'
3923 STATUS_UNDER_REVIEW = 'under_review'
3906
3924
3907 STATUSES = [
3925 STATUSES = [
3908 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3926 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3909 (STATUS_APPROVED, _("Approved")),
3927 (STATUS_APPROVED, _("Approved")),
3910 (STATUS_REJECTED, _("Rejected")),
3928 (STATUS_REJECTED, _("Rejected")),
3911 (STATUS_UNDER_REVIEW, _("Under Review")),
3929 (STATUS_UNDER_REVIEW, _("Under Review")),
3912 ]
3930 ]
3913
3931
3914 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3932 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3915 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3933 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3916 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3934 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3917 revision = Column('revision', String(40), nullable=False)
3935 revision = Column('revision', String(40), nullable=False)
3918 status = Column('status', String(128), nullable=False, default=DEFAULT)
3936 status = Column('status', String(128), nullable=False, default=DEFAULT)
3919 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3937 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3920 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3938 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3921 version = Column('version', Integer(), nullable=False, default=0)
3939 version = Column('version', Integer(), nullable=False, default=0)
3922 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3940 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3923
3941
3924 author = relationship('User', lazy='joined')
3942 author = relationship('User', lazy='joined')
3925 repo = relationship('Repository')
3943 repo = relationship('Repository')
3926 comment = relationship('ChangesetComment', lazy='joined')
3944 comment = relationship('ChangesetComment', lazy='joined')
3927 pull_request = relationship('PullRequest', lazy='joined')
3945 pull_request = relationship('PullRequest', lazy='joined')
3928
3946
3929 def __unicode__(self):
3947 def __unicode__(self):
3930 return u"<%s('%s[v%s]:%s')>" % (
3948 return u"<%s('%s[v%s]:%s')>" % (
3931 self.__class__.__name__,
3949 self.__class__.__name__,
3932 self.status, self.version, self.author
3950 self.status, self.version, self.author
3933 )
3951 )
3934
3952
3935 @classmethod
3953 @classmethod
3936 def get_status_lbl(cls, value):
3954 def get_status_lbl(cls, value):
3937 return dict(cls.STATUSES).get(value)
3955 return dict(cls.STATUSES).get(value)
3938
3956
3939 @property
3957 @property
3940 def status_lbl(self):
3958 def status_lbl(self):
3941 return ChangesetStatus.get_status_lbl(self.status)
3959 return ChangesetStatus.get_status_lbl(self.status)
3942
3960
3943 def get_api_data(self):
3961 def get_api_data(self):
3944 status = self
3962 status = self
3945 data = {
3963 data = {
3946 'status_id': status.changeset_status_id,
3964 'status_id': status.changeset_status_id,
3947 'status': status.status,
3965 'status': status.status,
3948 }
3966 }
3949 return data
3967 return data
3950
3968
3951 def __json__(self):
3969 def __json__(self):
3952 data = dict()
3970 data = dict()
3953 data.update(self.get_api_data())
3971 data.update(self.get_api_data())
3954 return data
3972 return data
3955
3973
3956
3974
3957 class _SetState(object):
3975 class _SetState(object):
3958 """
3976 """
3959 Context processor allowing changing state for sensitive operation such as
3977 Context processor allowing changing state for sensitive operation such as
3960 pull request update or merge
3978 pull request update or merge
3961 """
3979 """
3962
3980
3963 def __init__(self, pull_request, pr_state, back_state=None):
3981 def __init__(self, pull_request, pr_state, back_state=None):
3964 self._pr = pull_request
3982 self._pr = pull_request
3965 self._org_state = back_state or pull_request.pull_request_state
3983 self._org_state = back_state or pull_request.pull_request_state
3966 self._pr_state = pr_state
3984 self._pr_state = pr_state
3967 self._current_state = None
3985 self._current_state = None
3968
3986
3969 def __enter__(self):
3987 def __enter__(self):
3970 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3988 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3971 self._pr, self._pr_state)
3989 self._pr, self._pr_state)
3972 self.set_pr_state(self._pr_state)
3990 self.set_pr_state(self._pr_state)
3973 return self
3991 return self
3974
3992
3975 def __exit__(self, exc_type, exc_val, exc_tb):
3993 def __exit__(self, exc_type, exc_val, exc_tb):
3976 if exc_val is not None:
3994 if exc_val is not None:
3977 log.error(traceback.format_exc(exc_tb))
3995 log.error(traceback.format_exc(exc_tb))
3978 return None
3996 return None
3979
3997
3980 self.set_pr_state(self._org_state)
3998 self.set_pr_state(self._org_state)
3981 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3999 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3982 self._pr, self._org_state)
4000 self._pr, self._org_state)
3983
4001
3984 @property
4002 @property
3985 def state(self):
4003 def state(self):
3986 return self._current_state
4004 return self._current_state
3987
4005
3988 def set_pr_state(self, pr_state):
4006 def set_pr_state(self, pr_state):
3989 try:
4007 try:
3990 self._pr.pull_request_state = pr_state
4008 self._pr.pull_request_state = pr_state
3991 Session().add(self._pr)
4009 Session().add(self._pr)
3992 Session().commit()
4010 Session().commit()
3993 self._current_state = pr_state
4011 self._current_state = pr_state
3994 except Exception:
4012 except Exception:
3995 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4013 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3996 raise
4014 raise
3997
4015
3998
4016
3999 class _PullRequestBase(BaseModel):
4017 class _PullRequestBase(BaseModel):
4000 """
4018 """
4001 Common attributes of pull request and version entries.
4019 Common attributes of pull request and version entries.
4002 """
4020 """
4003
4021
4004 # .status values
4022 # .status values
4005 STATUS_NEW = u'new'
4023 STATUS_NEW = u'new'
4006 STATUS_OPEN = u'open'
4024 STATUS_OPEN = u'open'
4007 STATUS_CLOSED = u'closed'
4025 STATUS_CLOSED = u'closed'
4008
4026
4009 # available states
4027 # available states
4010 STATE_CREATING = u'creating'
4028 STATE_CREATING = u'creating'
4011 STATE_UPDATING = u'updating'
4029 STATE_UPDATING = u'updating'
4012 STATE_MERGING = u'merging'
4030 STATE_MERGING = u'merging'
4013 STATE_CREATED = u'created'
4031 STATE_CREATED = u'created'
4014
4032
4015 title = Column('title', Unicode(255), nullable=True)
4033 title = Column('title', Unicode(255), nullable=True)
4016 description = Column(
4034 description = Column(
4017 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4035 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4018 nullable=True)
4036 nullable=True)
4019 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4037 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4020
4038
4021 # new/open/closed status of pull request (not approve/reject/etc)
4039 # new/open/closed status of pull request (not approve/reject/etc)
4022 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4040 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4023 created_on = Column(
4041 created_on = Column(
4024 'created_on', DateTime(timezone=False), nullable=False,
4042 'created_on', DateTime(timezone=False), nullable=False,
4025 default=datetime.datetime.now)
4043 default=datetime.datetime.now)
4026 updated_on = Column(
4044 updated_on = Column(
4027 'updated_on', DateTime(timezone=False), nullable=False,
4045 'updated_on', DateTime(timezone=False), nullable=False,
4028 default=datetime.datetime.now)
4046 default=datetime.datetime.now)
4029
4047
4030 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4048 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4031
4049
4032 @declared_attr
4050 @declared_attr
4033 def user_id(cls):
4051 def user_id(cls):
4034 return Column(
4052 return Column(
4035 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4053 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4036 unique=None)
4054 unique=None)
4037
4055
4038 # 500 revisions max
4056 # 500 revisions max
4039 _revisions = Column(
4057 _revisions = Column(
4040 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4058 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4041
4059
4042 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4060 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4043
4061
4044 @declared_attr
4062 @declared_attr
4045 def source_repo_id(cls):
4063 def source_repo_id(cls):
4046 # TODO: dan: rename column to source_repo_id
4064 # TODO: dan: rename column to source_repo_id
4047 return Column(
4065 return Column(
4048 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4066 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4049 nullable=False)
4067 nullable=False)
4050
4068
4051 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4069 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4052
4070
4053 @hybrid_property
4071 @hybrid_property
4054 def source_ref(self):
4072 def source_ref(self):
4055 return self._source_ref
4073 return self._source_ref
4056
4074
4057 @source_ref.setter
4075 @source_ref.setter
4058 def source_ref(self, val):
4076 def source_ref(self, val):
4059 parts = (val or '').split(':')
4077 parts = (val or '').split(':')
4060 if len(parts) != 3:
4078 if len(parts) != 3:
4061 raise ValueError(
4079 raise ValueError(
4062 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4080 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4063 self._source_ref = safe_unicode(val)
4081 self._source_ref = safe_unicode(val)
4064
4082
4065 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4083 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4066
4084
4067 @hybrid_property
4085 @hybrid_property
4068 def target_ref(self):
4086 def target_ref(self):
4069 return self._target_ref
4087 return self._target_ref
4070
4088
4071 @target_ref.setter
4089 @target_ref.setter
4072 def target_ref(self, val):
4090 def target_ref(self, val):
4073 parts = (val or '').split(':')
4091 parts = (val or '').split(':')
4074 if len(parts) != 3:
4092 if len(parts) != 3:
4075 raise ValueError(
4093 raise ValueError(
4076 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4094 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4077 self._target_ref = safe_unicode(val)
4095 self._target_ref = safe_unicode(val)
4078
4096
4079 @declared_attr
4097 @declared_attr
4080 def target_repo_id(cls):
4098 def target_repo_id(cls):
4081 # TODO: dan: rename column to target_repo_id
4099 # TODO: dan: rename column to target_repo_id
4082 return Column(
4100 return Column(
4083 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4101 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4084 nullable=False)
4102 nullable=False)
4085
4103
4086 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4104 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4087
4105
4088 # TODO: dan: rename column to last_merge_source_rev
4106 # TODO: dan: rename column to last_merge_source_rev
4089 _last_merge_source_rev = Column(
4107 _last_merge_source_rev = Column(
4090 'last_merge_org_rev', String(40), nullable=True)
4108 'last_merge_org_rev', String(40), nullable=True)
4091 # TODO: dan: rename column to last_merge_target_rev
4109 # TODO: dan: rename column to last_merge_target_rev
4092 _last_merge_target_rev = Column(
4110 _last_merge_target_rev = Column(
4093 'last_merge_other_rev', String(40), nullable=True)
4111 'last_merge_other_rev', String(40), nullable=True)
4094 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4112 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4095 last_merge_metadata = Column(
4113 last_merge_metadata = Column(
4096 'last_merge_metadata', MutationObj.as_mutable(
4114 'last_merge_metadata', MutationObj.as_mutable(
4097 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4115 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4098
4116
4099 merge_rev = Column('merge_rev', String(40), nullable=True)
4117 merge_rev = Column('merge_rev', String(40), nullable=True)
4100
4118
4101 reviewer_data = Column(
4119 reviewer_data = Column(
4102 'reviewer_data_json', MutationObj.as_mutable(
4120 'reviewer_data_json', MutationObj.as_mutable(
4103 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4121 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4104
4122
4105 @property
4123 @property
4106 def reviewer_data_json(self):
4124 def reviewer_data_json(self):
4107 return json.dumps(self.reviewer_data)
4125 return json.dumps(self.reviewer_data)
4108
4126
4109 @property
4127 @property
4110 def work_in_progress(self):
4128 def work_in_progress(self):
4111 """checks if pull request is work in progress by checking the title"""
4129 """checks if pull request is work in progress by checking the title"""
4112 title = self.title.upper()
4130 title = self.title.upper()
4113 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4131 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4114 return True
4132 return True
4115 return False
4133 return False
4116
4134
4117 @hybrid_property
4135 @hybrid_property
4118 def description_safe(self):
4136 def description_safe(self):
4119 from rhodecode.lib import helpers as h
4137 from rhodecode.lib import helpers as h
4120 return h.escape(self.description)
4138 return h.escape(self.description)
4121
4139
4122 @hybrid_property
4140 @hybrid_property
4123 def revisions(self):
4141 def revisions(self):
4124 return self._revisions.split(':') if self._revisions else []
4142 return self._revisions.split(':') if self._revisions else []
4125
4143
4126 @revisions.setter
4144 @revisions.setter
4127 def revisions(self, val):
4145 def revisions(self, val):
4128 self._revisions = u':'.join(val)
4146 self._revisions = u':'.join(val)
4129
4147
4130 @hybrid_property
4148 @hybrid_property
4131 def last_merge_status(self):
4149 def last_merge_status(self):
4132 return safe_int(self._last_merge_status)
4150 return safe_int(self._last_merge_status)
4133
4151
4134 @last_merge_status.setter
4152 @last_merge_status.setter
4135 def last_merge_status(self, val):
4153 def last_merge_status(self, val):
4136 self._last_merge_status = val
4154 self._last_merge_status = val
4137
4155
4138 @declared_attr
4156 @declared_attr
4139 def author(cls):
4157 def author(cls):
4140 return relationship('User', lazy='joined')
4158 return relationship('User', lazy='joined')
4141
4159
4142 @declared_attr
4160 @declared_attr
4143 def source_repo(cls):
4161 def source_repo(cls):
4144 return relationship(
4162 return relationship(
4145 'Repository',
4163 'Repository',
4146 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4164 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4147
4165
4148 @property
4166 @property
4149 def source_ref_parts(self):
4167 def source_ref_parts(self):
4150 return self.unicode_to_reference(self.source_ref)
4168 return self.unicode_to_reference(self.source_ref)
4151
4169
4152 @declared_attr
4170 @declared_attr
4153 def target_repo(cls):
4171 def target_repo(cls):
4154 return relationship(
4172 return relationship(
4155 'Repository',
4173 'Repository',
4156 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4174 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4157
4175
4158 @property
4176 @property
4159 def target_ref_parts(self):
4177 def target_ref_parts(self):
4160 return self.unicode_to_reference(self.target_ref)
4178 return self.unicode_to_reference(self.target_ref)
4161
4179
4162 @property
4180 @property
4163 def shadow_merge_ref(self):
4181 def shadow_merge_ref(self):
4164 return self.unicode_to_reference(self._shadow_merge_ref)
4182 return self.unicode_to_reference(self._shadow_merge_ref)
4165
4183
4166 @shadow_merge_ref.setter
4184 @shadow_merge_ref.setter
4167 def shadow_merge_ref(self, ref):
4185 def shadow_merge_ref(self, ref):
4168 self._shadow_merge_ref = self.reference_to_unicode(ref)
4186 self._shadow_merge_ref = self.reference_to_unicode(ref)
4169
4187
4170 @staticmethod
4188 @staticmethod
4171 def unicode_to_reference(raw):
4189 def unicode_to_reference(raw):
4172 """
4190 """
4173 Convert a unicode (or string) to a reference object.
4191 Convert a unicode (or string) to a reference object.
4174 If unicode evaluates to False it returns None.
4192 If unicode evaluates to False it returns None.
4175 """
4193 """
4176 if raw:
4194 if raw:
4177 refs = raw.split(':')
4195 refs = raw.split(':')
4178 return Reference(*refs)
4196 return Reference(*refs)
4179 else:
4197 else:
4180 return None
4198 return None
4181
4199
4182 @staticmethod
4200 @staticmethod
4183 def reference_to_unicode(ref):
4201 def reference_to_unicode(ref):
4184 """
4202 """
4185 Convert a reference object to unicode.
4203 Convert a reference object to unicode.
4186 If reference is None it returns None.
4204 If reference is None it returns None.
4187 """
4205 """
4188 if ref:
4206 if ref:
4189 return u':'.join(ref)
4207 return u':'.join(ref)
4190 else:
4208 else:
4191 return None
4209 return None
4192
4210
4193 def get_api_data(self, with_merge_state=True):
4211 def get_api_data(self, with_merge_state=True):
4194 from rhodecode.model.pull_request import PullRequestModel
4212 from rhodecode.model.pull_request import PullRequestModel
4195
4213
4196 pull_request = self
4214 pull_request = self
4197 if with_merge_state:
4215 if with_merge_state:
4198 merge_response, merge_status, msg = \
4216 merge_response, merge_status, msg = \
4199 PullRequestModel().merge_status(pull_request)
4217 PullRequestModel().merge_status(pull_request)
4200 merge_state = {
4218 merge_state = {
4201 'status': merge_status,
4219 'status': merge_status,
4202 'message': safe_unicode(msg),
4220 'message': safe_unicode(msg),
4203 }
4221 }
4204 else:
4222 else:
4205 merge_state = {'status': 'not_available',
4223 merge_state = {'status': 'not_available',
4206 'message': 'not_available'}
4224 'message': 'not_available'}
4207
4225
4208 merge_data = {
4226 merge_data = {
4209 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4227 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4210 'reference': (
4228 'reference': (
4211 pull_request.shadow_merge_ref._asdict()
4229 pull_request.shadow_merge_ref._asdict()
4212 if pull_request.shadow_merge_ref else None),
4230 if pull_request.shadow_merge_ref else None),
4213 }
4231 }
4214
4232
4215 data = {
4233 data = {
4216 'pull_request_id': pull_request.pull_request_id,
4234 'pull_request_id': pull_request.pull_request_id,
4217 'url': PullRequestModel().get_url(pull_request),
4235 'url': PullRequestModel().get_url(pull_request),
4218 'title': pull_request.title,
4236 'title': pull_request.title,
4219 'description': pull_request.description,
4237 'description': pull_request.description,
4220 'status': pull_request.status,
4238 'status': pull_request.status,
4221 'state': pull_request.pull_request_state,
4239 'state': pull_request.pull_request_state,
4222 'created_on': pull_request.created_on,
4240 'created_on': pull_request.created_on,
4223 'updated_on': pull_request.updated_on,
4241 'updated_on': pull_request.updated_on,
4224 'commit_ids': pull_request.revisions,
4242 'commit_ids': pull_request.revisions,
4225 'review_status': pull_request.calculated_review_status(),
4243 'review_status': pull_request.calculated_review_status(),
4226 'mergeable': merge_state,
4244 'mergeable': merge_state,
4227 'source': {
4245 'source': {
4228 'clone_url': pull_request.source_repo.clone_url(),
4246 'clone_url': pull_request.source_repo.clone_url(),
4229 'repository': pull_request.source_repo.repo_name,
4247 'repository': pull_request.source_repo.repo_name,
4230 'reference': {
4248 'reference': {
4231 'name': pull_request.source_ref_parts.name,
4249 'name': pull_request.source_ref_parts.name,
4232 'type': pull_request.source_ref_parts.type,
4250 'type': pull_request.source_ref_parts.type,
4233 'commit_id': pull_request.source_ref_parts.commit_id,
4251 'commit_id': pull_request.source_ref_parts.commit_id,
4234 },
4252 },
4235 },
4253 },
4236 'target': {
4254 'target': {
4237 'clone_url': pull_request.target_repo.clone_url(),
4255 'clone_url': pull_request.target_repo.clone_url(),
4238 'repository': pull_request.target_repo.repo_name,
4256 'repository': pull_request.target_repo.repo_name,
4239 'reference': {
4257 'reference': {
4240 'name': pull_request.target_ref_parts.name,
4258 'name': pull_request.target_ref_parts.name,
4241 'type': pull_request.target_ref_parts.type,
4259 'type': pull_request.target_ref_parts.type,
4242 'commit_id': pull_request.target_ref_parts.commit_id,
4260 'commit_id': pull_request.target_ref_parts.commit_id,
4243 },
4261 },
4244 },
4262 },
4245 'merge': merge_data,
4263 'merge': merge_data,
4246 'author': pull_request.author.get_api_data(include_secrets=False,
4264 'author': pull_request.author.get_api_data(include_secrets=False,
4247 details='basic'),
4265 details='basic'),
4248 'reviewers': [
4266 'reviewers': [
4249 {
4267 {
4250 'user': reviewer.get_api_data(include_secrets=False,
4268 'user': reviewer.get_api_data(include_secrets=False,
4251 details='basic'),
4269 details='basic'),
4252 'reasons': reasons,
4270 'reasons': reasons,
4253 'review_status': st[0][1].status if st else 'not_reviewed',
4271 'review_status': st[0][1].status if st else 'not_reviewed',
4254 }
4272 }
4255 for obj, reviewer, reasons, mandatory, st in
4273 for obj, reviewer, reasons, mandatory, st in
4256 pull_request.reviewers_statuses()
4274 pull_request.reviewers_statuses()
4257 ]
4275 ]
4258 }
4276 }
4259
4277
4260 return data
4278 return data
4261
4279
4262 def set_state(self, pull_request_state, final_state=None):
4280 def set_state(self, pull_request_state, final_state=None):
4263 """
4281 """
4264 # goes from initial state to updating to initial state.
4282 # goes from initial state to updating to initial state.
4265 # initial state can be changed by specifying back_state=
4283 # initial state can be changed by specifying back_state=
4266 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4284 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4267 pull_request.merge()
4285 pull_request.merge()
4268
4286
4269 :param pull_request_state:
4287 :param pull_request_state:
4270 :param final_state:
4288 :param final_state:
4271
4289
4272 """
4290 """
4273
4291
4274 return _SetState(self, pull_request_state, back_state=final_state)
4292 return _SetState(self, pull_request_state, back_state=final_state)
4275
4293
4276
4294
4277 class PullRequest(Base, _PullRequestBase):
4295 class PullRequest(Base, _PullRequestBase):
4278 __tablename__ = 'pull_requests'
4296 __tablename__ = 'pull_requests'
4279 __table_args__ = (
4297 __table_args__ = (
4280 base_table_args,
4298 base_table_args,
4281 )
4299 )
4282
4300
4283 pull_request_id = Column(
4301 pull_request_id = Column(
4284 'pull_request_id', Integer(), nullable=False, primary_key=True)
4302 'pull_request_id', Integer(), nullable=False, primary_key=True)
4285
4303
4286 def __repr__(self):
4304 def __repr__(self):
4287 if self.pull_request_id:
4305 if self.pull_request_id:
4288 return '<DB:PullRequest #%s>' % self.pull_request_id
4306 return '<DB:PullRequest #%s>' % self.pull_request_id
4289 else:
4307 else:
4290 return '<DB:PullRequest at %#x>' % id(self)
4308 return '<DB:PullRequest at %#x>' % id(self)
4291
4309
4292 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4310 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4293 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4311 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4294 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4312 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4295 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4313 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4296 lazy='dynamic')
4314 lazy='dynamic')
4297
4315
4298 @classmethod
4316 @classmethod
4299 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4317 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4300 internal_methods=None):
4318 internal_methods=None):
4301
4319
4302 class PullRequestDisplay(object):
4320 class PullRequestDisplay(object):
4303 """
4321 """
4304 Special object wrapper for showing PullRequest data via Versions
4322 Special object wrapper for showing PullRequest data via Versions
4305 It mimics PR object as close as possible. This is read only object
4323 It mimics PR object as close as possible. This is read only object
4306 just for display
4324 just for display
4307 """
4325 """
4308
4326
4309 def __init__(self, attrs, internal=None):
4327 def __init__(self, attrs, internal=None):
4310 self.attrs = attrs
4328 self.attrs = attrs
4311 # internal have priority over the given ones via attrs
4329 # internal have priority over the given ones via attrs
4312 self.internal = internal or ['versions']
4330 self.internal = internal or ['versions']
4313
4331
4314 def __getattr__(self, item):
4332 def __getattr__(self, item):
4315 if item in self.internal:
4333 if item in self.internal:
4316 return getattr(self, item)
4334 return getattr(self, item)
4317 try:
4335 try:
4318 return self.attrs[item]
4336 return self.attrs[item]
4319 except KeyError:
4337 except KeyError:
4320 raise AttributeError(
4338 raise AttributeError(
4321 '%s object has no attribute %s' % (self, item))
4339 '%s object has no attribute %s' % (self, item))
4322
4340
4323 def __repr__(self):
4341 def __repr__(self):
4324 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4342 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4325
4343
4326 def versions(self):
4344 def versions(self):
4327 return pull_request_obj.versions.order_by(
4345 return pull_request_obj.versions.order_by(
4328 PullRequestVersion.pull_request_version_id).all()
4346 PullRequestVersion.pull_request_version_id).all()
4329
4347
4330 def is_closed(self):
4348 def is_closed(self):
4331 return pull_request_obj.is_closed()
4349 return pull_request_obj.is_closed()
4332
4350
4333 def is_state_changing(self):
4351 def is_state_changing(self):
4334 return pull_request_obj.is_state_changing()
4352 return pull_request_obj.is_state_changing()
4335
4353
4336 @property
4354 @property
4337 def pull_request_version_id(self):
4355 def pull_request_version_id(self):
4338 return getattr(pull_request_obj, 'pull_request_version_id', None)
4356 return getattr(pull_request_obj, 'pull_request_version_id', None)
4339
4357
4340 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4358 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4341
4359
4342 attrs.author = StrictAttributeDict(
4360 attrs.author = StrictAttributeDict(
4343 pull_request_obj.author.get_api_data())
4361 pull_request_obj.author.get_api_data())
4344 if pull_request_obj.target_repo:
4362 if pull_request_obj.target_repo:
4345 attrs.target_repo = StrictAttributeDict(
4363 attrs.target_repo = StrictAttributeDict(
4346 pull_request_obj.target_repo.get_api_data())
4364 pull_request_obj.target_repo.get_api_data())
4347 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4365 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4348
4366
4349 if pull_request_obj.source_repo:
4367 if pull_request_obj.source_repo:
4350 attrs.source_repo = StrictAttributeDict(
4368 attrs.source_repo = StrictAttributeDict(
4351 pull_request_obj.source_repo.get_api_data())
4369 pull_request_obj.source_repo.get_api_data())
4352 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4370 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4353
4371
4354 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4372 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4355 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4373 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4356 attrs.revisions = pull_request_obj.revisions
4374 attrs.revisions = pull_request_obj.revisions
4357 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4375 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4358 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4376 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4359 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4377 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4360 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4378 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4361
4379
4362 return PullRequestDisplay(attrs, internal=internal_methods)
4380 return PullRequestDisplay(attrs, internal=internal_methods)
4363
4381
4364 def is_closed(self):
4382 def is_closed(self):
4365 return self.status == self.STATUS_CLOSED
4383 return self.status == self.STATUS_CLOSED
4366
4384
4367 def is_state_changing(self):
4385 def is_state_changing(self):
4368 return self.pull_request_state != PullRequest.STATE_CREATED
4386 return self.pull_request_state != PullRequest.STATE_CREATED
4369
4387
4370 def __json__(self):
4388 def __json__(self):
4371 return {
4389 return {
4372 'revisions': self.revisions,
4390 'revisions': self.revisions,
4373 'versions': self.versions_count
4391 'versions': self.versions_count
4374 }
4392 }
4375
4393
4376 def calculated_review_status(self):
4394 def calculated_review_status(self):
4377 from rhodecode.model.changeset_status import ChangesetStatusModel
4395 from rhodecode.model.changeset_status import ChangesetStatusModel
4378 return ChangesetStatusModel().calculated_review_status(self)
4396 return ChangesetStatusModel().calculated_review_status(self)
4379
4397
4380 def reviewers_statuses(self):
4398 def reviewers_statuses(self):
4381 from rhodecode.model.changeset_status import ChangesetStatusModel
4399 from rhodecode.model.changeset_status import ChangesetStatusModel
4382 return ChangesetStatusModel().reviewers_statuses(self)
4400 return ChangesetStatusModel().reviewers_statuses(self)
4383
4401
4384 @property
4402 @property
4385 def workspace_id(self):
4403 def workspace_id(self):
4386 from rhodecode.model.pull_request import PullRequestModel
4404 from rhodecode.model.pull_request import PullRequestModel
4387 return PullRequestModel()._workspace_id(self)
4405 return PullRequestModel()._workspace_id(self)
4388
4406
4389 def get_shadow_repo(self):
4407 def get_shadow_repo(self):
4390 workspace_id = self.workspace_id
4408 workspace_id = self.workspace_id
4391 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4409 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4392 if os.path.isdir(shadow_repository_path):
4410 if os.path.isdir(shadow_repository_path):
4393 vcs_obj = self.target_repo.scm_instance()
4411 vcs_obj = self.target_repo.scm_instance()
4394 return vcs_obj.get_shadow_instance(shadow_repository_path)
4412 return vcs_obj.get_shadow_instance(shadow_repository_path)
4395
4413
4396 @property
4414 @property
4397 def versions_count(self):
4415 def versions_count(self):
4398 """
4416 """
4399 return number of versions this PR have, e.g a PR that once been
4417 return number of versions this PR have, e.g a PR that once been
4400 updated will have 2 versions
4418 updated will have 2 versions
4401 """
4419 """
4402 return self.versions.count() + 1
4420 return self.versions.count() + 1
4403
4421
4404
4422
4405 class PullRequestVersion(Base, _PullRequestBase):
4423 class PullRequestVersion(Base, _PullRequestBase):
4406 __tablename__ = 'pull_request_versions'
4424 __tablename__ = 'pull_request_versions'
4407 __table_args__ = (
4425 __table_args__ = (
4408 base_table_args,
4426 base_table_args,
4409 )
4427 )
4410
4428
4411 pull_request_version_id = Column(
4429 pull_request_version_id = Column(
4412 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4430 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4413 pull_request_id = Column(
4431 pull_request_id = Column(
4414 'pull_request_id', Integer(),
4432 'pull_request_id', Integer(),
4415 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4433 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4416 pull_request = relationship('PullRequest')
4434 pull_request = relationship('PullRequest')
4417
4435
4418 def __repr__(self):
4436 def __repr__(self):
4419 if self.pull_request_version_id:
4437 if self.pull_request_version_id:
4420 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4438 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4421 else:
4439 else:
4422 return '<DB:PullRequestVersion at %#x>' % id(self)
4440 return '<DB:PullRequestVersion at %#x>' % id(self)
4423
4441
4424 @property
4442 @property
4425 def reviewers(self):
4443 def reviewers(self):
4426 return self.pull_request.reviewers
4444 return self.pull_request.reviewers
4427
4445
4428 @property
4446 @property
4429 def versions(self):
4447 def versions(self):
4430 return self.pull_request.versions
4448 return self.pull_request.versions
4431
4449
4432 def is_closed(self):
4450 def is_closed(self):
4433 # calculate from original
4451 # calculate from original
4434 return self.pull_request.status == self.STATUS_CLOSED
4452 return self.pull_request.status == self.STATUS_CLOSED
4435
4453
4436 def is_state_changing(self):
4454 def is_state_changing(self):
4437 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4455 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4438
4456
4439 def calculated_review_status(self):
4457 def calculated_review_status(self):
4440 return self.pull_request.calculated_review_status()
4458 return self.pull_request.calculated_review_status()
4441
4459
4442 def reviewers_statuses(self):
4460 def reviewers_statuses(self):
4443 return self.pull_request.reviewers_statuses()
4461 return self.pull_request.reviewers_statuses()
4444
4462
4445
4463
4446 class PullRequestReviewers(Base, BaseModel):
4464 class PullRequestReviewers(Base, BaseModel):
4447 __tablename__ = 'pull_request_reviewers'
4465 __tablename__ = 'pull_request_reviewers'
4448 __table_args__ = (
4466 __table_args__ = (
4449 base_table_args,
4467 base_table_args,
4450 )
4468 )
4451
4469
4452 @hybrid_property
4470 @hybrid_property
4453 def reasons(self):
4471 def reasons(self):
4454 if not self._reasons:
4472 if not self._reasons:
4455 return []
4473 return []
4456 return self._reasons
4474 return self._reasons
4457
4475
4458 @reasons.setter
4476 @reasons.setter
4459 def reasons(self, val):
4477 def reasons(self, val):
4460 val = val or []
4478 val = val or []
4461 if any(not isinstance(x, compat.string_types) for x in val):
4479 if any(not isinstance(x, compat.string_types) for x in val):
4462 raise Exception('invalid reasons type, must be list of strings')
4480 raise Exception('invalid reasons type, must be list of strings')
4463 self._reasons = val
4481 self._reasons = val
4464
4482
4465 pull_requests_reviewers_id = Column(
4483 pull_requests_reviewers_id = Column(
4466 'pull_requests_reviewers_id', Integer(), nullable=False,
4484 'pull_requests_reviewers_id', Integer(), nullable=False,
4467 primary_key=True)
4485 primary_key=True)
4468 pull_request_id = Column(
4486 pull_request_id = Column(
4469 "pull_request_id", Integer(),
4487 "pull_request_id", Integer(),
4470 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4488 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4471 user_id = Column(
4489 user_id = Column(
4472 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4490 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4473 _reasons = Column(
4491 _reasons = Column(
4474 'reason', MutationList.as_mutable(
4492 'reason', MutationList.as_mutable(
4475 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4493 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4476
4494
4477 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4495 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4478 user = relationship('User')
4496 user = relationship('User')
4479 pull_request = relationship('PullRequest')
4497 pull_request = relationship('PullRequest')
4480
4498
4481 rule_data = Column(
4499 rule_data = Column(
4482 'rule_data_json',
4500 'rule_data_json',
4483 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4501 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4484
4502
4485 def rule_user_group_data(self):
4503 def rule_user_group_data(self):
4486 """
4504 """
4487 Returns the voting user group rule data for this reviewer
4505 Returns the voting user group rule data for this reviewer
4488 """
4506 """
4489
4507
4490 if self.rule_data and 'vote_rule' in self.rule_data:
4508 if self.rule_data and 'vote_rule' in self.rule_data:
4491 user_group_data = {}
4509 user_group_data = {}
4492 if 'rule_user_group_entry_id' in self.rule_data:
4510 if 'rule_user_group_entry_id' in self.rule_data:
4493 # means a group with voting rules !
4511 # means a group with voting rules !
4494 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4512 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4495 user_group_data['name'] = self.rule_data['rule_name']
4513 user_group_data['name'] = self.rule_data['rule_name']
4496 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4514 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4497
4515
4498 return user_group_data
4516 return user_group_data
4499
4517
4500 def __unicode__(self):
4518 def __unicode__(self):
4501 return u"<%s('id:%s')>" % (self.__class__.__name__,
4519 return u"<%s('id:%s')>" % (self.__class__.__name__,
4502 self.pull_requests_reviewers_id)
4520 self.pull_requests_reviewers_id)
4503
4521
4504
4522
4505 class Notification(Base, BaseModel):
4523 class Notification(Base, BaseModel):
4506 __tablename__ = 'notifications'
4524 __tablename__ = 'notifications'
4507 __table_args__ = (
4525 __table_args__ = (
4508 Index('notification_type_idx', 'type'),
4526 Index('notification_type_idx', 'type'),
4509 base_table_args,
4527 base_table_args,
4510 )
4528 )
4511
4529
4512 TYPE_CHANGESET_COMMENT = u'cs_comment'
4530 TYPE_CHANGESET_COMMENT = u'cs_comment'
4513 TYPE_MESSAGE = u'message'
4531 TYPE_MESSAGE = u'message'
4514 TYPE_MENTION = u'mention'
4532 TYPE_MENTION = u'mention'
4515 TYPE_REGISTRATION = u'registration'
4533 TYPE_REGISTRATION = u'registration'
4516 TYPE_PULL_REQUEST = u'pull_request'
4534 TYPE_PULL_REQUEST = u'pull_request'
4517 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4535 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4518 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4536 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4519
4537
4520 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4538 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4521 subject = Column('subject', Unicode(512), nullable=True)
4539 subject = Column('subject', Unicode(512), nullable=True)
4522 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4540 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4523 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4541 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4542 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4525 type_ = Column('type', Unicode(255))
4543 type_ = Column('type', Unicode(255))
4526
4544
4527 created_by_user = relationship('User')
4545 created_by_user = relationship('User')
4528 notifications_to_users = relationship('UserNotification', lazy='joined',
4546 notifications_to_users = relationship('UserNotification', lazy='joined',
4529 cascade="all, delete-orphan")
4547 cascade="all, delete-orphan")
4530
4548
4531 @property
4549 @property
4532 def recipients(self):
4550 def recipients(self):
4533 return [x.user for x in UserNotification.query()\
4551 return [x.user for x in UserNotification.query()\
4534 .filter(UserNotification.notification == self)\
4552 .filter(UserNotification.notification == self)\
4535 .order_by(UserNotification.user_id.asc()).all()]
4553 .order_by(UserNotification.user_id.asc()).all()]
4536
4554
4537 @classmethod
4555 @classmethod
4538 def create(cls, created_by, subject, body, recipients, type_=None):
4556 def create(cls, created_by, subject, body, recipients, type_=None):
4539 if type_ is None:
4557 if type_ is None:
4540 type_ = Notification.TYPE_MESSAGE
4558 type_ = Notification.TYPE_MESSAGE
4541
4559
4542 notification = cls()
4560 notification = cls()
4543 notification.created_by_user = created_by
4561 notification.created_by_user = created_by
4544 notification.subject = subject
4562 notification.subject = subject
4545 notification.body = body
4563 notification.body = body
4546 notification.type_ = type_
4564 notification.type_ = type_
4547 notification.created_on = datetime.datetime.now()
4565 notification.created_on = datetime.datetime.now()
4548
4566
4549 # For each recipient link the created notification to his account
4567 # For each recipient link the created notification to his account
4550 for u in recipients:
4568 for u in recipients:
4551 assoc = UserNotification()
4569 assoc = UserNotification()
4552 assoc.user_id = u.user_id
4570 assoc.user_id = u.user_id
4553 assoc.notification = notification
4571 assoc.notification = notification
4554
4572
4555 # if created_by is inside recipients mark his notification
4573 # if created_by is inside recipients mark his notification
4556 # as read
4574 # as read
4557 if u.user_id == created_by.user_id:
4575 if u.user_id == created_by.user_id:
4558 assoc.read = True
4576 assoc.read = True
4559 Session().add(assoc)
4577 Session().add(assoc)
4560
4578
4561 Session().add(notification)
4579 Session().add(notification)
4562
4580
4563 return notification
4581 return notification
4564
4582
4565
4583
4566 class UserNotification(Base, BaseModel):
4584 class UserNotification(Base, BaseModel):
4567 __tablename__ = 'user_to_notification'
4585 __tablename__ = 'user_to_notification'
4568 __table_args__ = (
4586 __table_args__ = (
4569 UniqueConstraint('user_id', 'notification_id'),
4587 UniqueConstraint('user_id', 'notification_id'),
4570 base_table_args
4588 base_table_args
4571 )
4589 )
4572
4590
4573 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4591 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4574 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4592 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4575 read = Column('read', Boolean, default=False)
4593 read = Column('read', Boolean, default=False)
4576 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4594 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4577
4595
4578 user = relationship('User', lazy="joined")
4596 user = relationship('User', lazy="joined")
4579 notification = relationship('Notification', lazy="joined",
4597 notification = relationship('Notification', lazy="joined",
4580 order_by=lambda: Notification.created_on.desc(),)
4598 order_by=lambda: Notification.created_on.desc(),)
4581
4599
4582 def mark_as_read(self):
4600 def mark_as_read(self):
4583 self.read = True
4601 self.read = True
4584 Session().add(self)
4602 Session().add(self)
4585
4603
4586
4604
4587 class UserNotice(Base, BaseModel):
4605 class UserNotice(Base, BaseModel):
4588 __tablename__ = 'user_notices'
4606 __tablename__ = 'user_notices'
4589 __table_args__ = (
4607 __table_args__ = (
4590 base_table_args
4608 base_table_args
4591 )
4609 )
4592
4610
4593 NOTIFICATION_TYPE_MESSAGE = 'message'
4611 NOTIFICATION_TYPE_MESSAGE = 'message'
4594 NOTIFICATION_TYPE_NOTICE = 'notice'
4612 NOTIFICATION_TYPE_NOTICE = 'notice'
4595
4613
4596 NOTIFICATION_LEVEL_INFO = 'info'
4614 NOTIFICATION_LEVEL_INFO = 'info'
4597 NOTIFICATION_LEVEL_WARNING = 'warning'
4615 NOTIFICATION_LEVEL_WARNING = 'warning'
4598 NOTIFICATION_LEVEL_ERROR = 'error'
4616 NOTIFICATION_LEVEL_ERROR = 'error'
4599
4617
4600 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4618 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4601
4619
4602 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4620 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4603 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4621 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4604
4622
4605 notice_read = Column('notice_read', Boolean, default=False)
4623 notice_read = Column('notice_read', Boolean, default=False)
4606
4624
4607 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4625 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4608 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4626 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4609
4627
4610 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4628 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4611 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4629 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4612
4630
4613 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4631 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4614 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4632 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4615
4633
4616 @classmethod
4634 @classmethod
4617 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4635 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4618
4636
4619 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4637 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4620 cls.NOTIFICATION_LEVEL_WARNING,
4638 cls.NOTIFICATION_LEVEL_WARNING,
4621 cls.NOTIFICATION_LEVEL_INFO]:
4639 cls.NOTIFICATION_LEVEL_INFO]:
4622 return
4640 return
4623
4641
4624 from rhodecode.model.user import UserModel
4642 from rhodecode.model.user import UserModel
4625 user = UserModel().get_user(user)
4643 user = UserModel().get_user(user)
4626
4644
4627 new_notice = UserNotice()
4645 new_notice = UserNotice()
4628 if not allow_duplicate:
4646 if not allow_duplicate:
4629 existing_msg = UserNotice().query() \
4647 existing_msg = UserNotice().query() \
4630 .filter(UserNotice.user == user) \
4648 .filter(UserNotice.user == user) \
4631 .filter(UserNotice.notice_body == body) \
4649 .filter(UserNotice.notice_body == body) \
4632 .filter(UserNotice.notice_read == false()) \
4650 .filter(UserNotice.notice_read == false()) \
4633 .scalar()
4651 .scalar()
4634 if existing_msg:
4652 if existing_msg:
4635 log.warning('Ignoring duplicate notice for user %s', user)
4653 log.warning('Ignoring duplicate notice for user %s', user)
4636 return
4654 return
4637
4655
4638 new_notice.user = user
4656 new_notice.user = user
4639 new_notice.notice_subject = subject
4657 new_notice.notice_subject = subject
4640 new_notice.notice_body = body
4658 new_notice.notice_body = body
4641 new_notice.notification_level = notice_level
4659 new_notice.notification_level = notice_level
4642 Session().add(new_notice)
4660 Session().add(new_notice)
4643 Session().commit()
4661 Session().commit()
4644
4662
4645
4663
4646 class Gist(Base, BaseModel):
4664 class Gist(Base, BaseModel):
4647 __tablename__ = 'gists'
4665 __tablename__ = 'gists'
4648 __table_args__ = (
4666 __table_args__ = (
4649 Index('g_gist_access_id_idx', 'gist_access_id'),
4667 Index('g_gist_access_id_idx', 'gist_access_id'),
4650 Index('g_created_on_idx', 'created_on'),
4668 Index('g_created_on_idx', 'created_on'),
4651 base_table_args
4669 base_table_args
4652 )
4670 )
4653
4671
4654 GIST_PUBLIC = u'public'
4672 GIST_PUBLIC = u'public'
4655 GIST_PRIVATE = u'private'
4673 GIST_PRIVATE = u'private'
4656 DEFAULT_FILENAME = u'gistfile1.txt'
4674 DEFAULT_FILENAME = u'gistfile1.txt'
4657
4675
4658 ACL_LEVEL_PUBLIC = u'acl_public'
4676 ACL_LEVEL_PUBLIC = u'acl_public'
4659 ACL_LEVEL_PRIVATE = u'acl_private'
4677 ACL_LEVEL_PRIVATE = u'acl_private'
4660
4678
4661 gist_id = Column('gist_id', Integer(), primary_key=True)
4679 gist_id = Column('gist_id', Integer(), primary_key=True)
4662 gist_access_id = Column('gist_access_id', Unicode(250))
4680 gist_access_id = Column('gist_access_id', Unicode(250))
4663 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4681 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4664 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4682 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4665 gist_expires = Column('gist_expires', Float(53), nullable=False)
4683 gist_expires = Column('gist_expires', Float(53), nullable=False)
4666 gist_type = Column('gist_type', Unicode(128), nullable=False)
4684 gist_type = Column('gist_type', Unicode(128), nullable=False)
4667 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4685 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4668 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4686 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4669 acl_level = Column('acl_level', Unicode(128), nullable=True)
4687 acl_level = Column('acl_level', Unicode(128), nullable=True)
4670
4688
4671 owner = relationship('User')
4689 owner = relationship('User')
4672
4690
4673 def __repr__(self):
4691 def __repr__(self):
4674 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4692 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4675
4693
4676 @hybrid_property
4694 @hybrid_property
4677 def description_safe(self):
4695 def description_safe(self):
4678 from rhodecode.lib import helpers as h
4696 from rhodecode.lib import helpers as h
4679 return h.escape(self.gist_description)
4697 return h.escape(self.gist_description)
4680
4698
4681 @classmethod
4699 @classmethod
4682 def get_or_404(cls, id_):
4700 def get_or_404(cls, id_):
4683 from pyramid.httpexceptions import HTTPNotFound
4701 from pyramid.httpexceptions import HTTPNotFound
4684
4702
4685 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4703 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4686 if not res:
4704 if not res:
4687 raise HTTPNotFound()
4705 raise HTTPNotFound()
4688 return res
4706 return res
4689
4707
4690 @classmethod
4708 @classmethod
4691 def get_by_access_id(cls, gist_access_id):
4709 def get_by_access_id(cls, gist_access_id):
4692 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4710 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4693
4711
4694 def gist_url(self):
4712 def gist_url(self):
4695 from rhodecode.model.gist import GistModel
4713 from rhodecode.model.gist import GistModel
4696 return GistModel().get_url(self)
4714 return GistModel().get_url(self)
4697
4715
4698 @classmethod
4716 @classmethod
4699 def base_path(cls):
4717 def base_path(cls):
4700 """
4718 """
4701 Returns base path when all gists are stored
4719 Returns base path when all gists are stored
4702
4720
4703 :param cls:
4721 :param cls:
4704 """
4722 """
4705 from rhodecode.model.gist import GIST_STORE_LOC
4723 from rhodecode.model.gist import GIST_STORE_LOC
4706 q = Session().query(RhodeCodeUi)\
4724 q = Session().query(RhodeCodeUi)\
4707 .filter(RhodeCodeUi.ui_key == URL_SEP)
4725 .filter(RhodeCodeUi.ui_key == URL_SEP)
4708 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4726 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4709 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4727 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4710
4728
4711 def get_api_data(self):
4729 def get_api_data(self):
4712 """
4730 """
4713 Common function for generating gist related data for API
4731 Common function for generating gist related data for API
4714 """
4732 """
4715 gist = self
4733 gist = self
4716 data = {
4734 data = {
4717 'gist_id': gist.gist_id,
4735 'gist_id': gist.gist_id,
4718 'type': gist.gist_type,
4736 'type': gist.gist_type,
4719 'access_id': gist.gist_access_id,
4737 'access_id': gist.gist_access_id,
4720 'description': gist.gist_description,
4738 'description': gist.gist_description,
4721 'url': gist.gist_url(),
4739 'url': gist.gist_url(),
4722 'expires': gist.gist_expires,
4740 'expires': gist.gist_expires,
4723 'created_on': gist.created_on,
4741 'created_on': gist.created_on,
4724 'modified_at': gist.modified_at,
4742 'modified_at': gist.modified_at,
4725 'content': None,
4743 'content': None,
4726 'acl_level': gist.acl_level,
4744 'acl_level': gist.acl_level,
4727 }
4745 }
4728 return data
4746 return data
4729
4747
4730 def __json__(self):
4748 def __json__(self):
4731 data = dict(
4749 data = dict(
4732 )
4750 )
4733 data.update(self.get_api_data())
4751 data.update(self.get_api_data())
4734 return data
4752 return data
4735 # SCM functions
4753 # SCM functions
4736
4754
4737 def scm_instance(self, **kwargs):
4755 def scm_instance(self, **kwargs):
4738 """
4756 """
4739 Get an instance of VCS Repository
4757 Get an instance of VCS Repository
4740
4758
4741 :param kwargs:
4759 :param kwargs:
4742 """
4760 """
4743 from rhodecode.model.gist import GistModel
4761 from rhodecode.model.gist import GistModel
4744 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4762 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4745 return get_vcs_instance(
4763 return get_vcs_instance(
4746 repo_path=safe_str(full_repo_path), create=False,
4764 repo_path=safe_str(full_repo_path), create=False,
4747 _vcs_alias=GistModel.vcs_backend)
4765 _vcs_alias=GistModel.vcs_backend)
4748
4766
4749
4767
4750 class ExternalIdentity(Base, BaseModel):
4768 class ExternalIdentity(Base, BaseModel):
4751 __tablename__ = 'external_identities'
4769 __tablename__ = 'external_identities'
4752 __table_args__ = (
4770 __table_args__ = (
4753 Index('local_user_id_idx', 'local_user_id'),
4771 Index('local_user_id_idx', 'local_user_id'),
4754 Index('external_id_idx', 'external_id'),
4772 Index('external_id_idx', 'external_id'),
4755 base_table_args
4773 base_table_args
4756 )
4774 )
4757
4775
4758 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4776 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4759 external_username = Column('external_username', Unicode(1024), default=u'')
4777 external_username = Column('external_username', Unicode(1024), default=u'')
4760 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4778 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4761 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4779 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4762 access_token = Column('access_token', String(1024), default=u'')
4780 access_token = Column('access_token', String(1024), default=u'')
4763 alt_token = Column('alt_token', String(1024), default=u'')
4781 alt_token = Column('alt_token', String(1024), default=u'')
4764 token_secret = Column('token_secret', String(1024), default=u'')
4782 token_secret = Column('token_secret', String(1024), default=u'')
4765
4783
4766 @classmethod
4784 @classmethod
4767 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4785 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4768 """
4786 """
4769 Returns ExternalIdentity instance based on search params
4787 Returns ExternalIdentity instance based on search params
4770
4788
4771 :param external_id:
4789 :param external_id:
4772 :param provider_name:
4790 :param provider_name:
4773 :return: ExternalIdentity
4791 :return: ExternalIdentity
4774 """
4792 """
4775 query = cls.query()
4793 query = cls.query()
4776 query = query.filter(cls.external_id == external_id)
4794 query = query.filter(cls.external_id == external_id)
4777 query = query.filter(cls.provider_name == provider_name)
4795 query = query.filter(cls.provider_name == provider_name)
4778 if local_user_id:
4796 if local_user_id:
4779 query = query.filter(cls.local_user_id == local_user_id)
4797 query = query.filter(cls.local_user_id == local_user_id)
4780 return query.first()
4798 return query.first()
4781
4799
4782 @classmethod
4800 @classmethod
4783 def user_by_external_id_and_provider(cls, external_id, provider_name):
4801 def user_by_external_id_and_provider(cls, external_id, provider_name):
4784 """
4802 """
4785 Returns User instance based on search params
4803 Returns User instance based on search params
4786
4804
4787 :param external_id:
4805 :param external_id:
4788 :param provider_name:
4806 :param provider_name:
4789 :return: User
4807 :return: User
4790 """
4808 """
4791 query = User.query()
4809 query = User.query()
4792 query = query.filter(cls.external_id == external_id)
4810 query = query.filter(cls.external_id == external_id)
4793 query = query.filter(cls.provider_name == provider_name)
4811 query = query.filter(cls.provider_name == provider_name)
4794 query = query.filter(User.user_id == cls.local_user_id)
4812 query = query.filter(User.user_id == cls.local_user_id)
4795 return query.first()
4813 return query.first()
4796
4814
4797 @classmethod
4815 @classmethod
4798 def by_local_user_id(cls, local_user_id):
4816 def by_local_user_id(cls, local_user_id):
4799 """
4817 """
4800 Returns all tokens for user
4818 Returns all tokens for user
4801
4819
4802 :param local_user_id:
4820 :param local_user_id:
4803 :return: ExternalIdentity
4821 :return: ExternalIdentity
4804 """
4822 """
4805 query = cls.query()
4823 query = cls.query()
4806 query = query.filter(cls.local_user_id == local_user_id)
4824 query = query.filter(cls.local_user_id == local_user_id)
4807 return query
4825 return query
4808
4826
4809 @classmethod
4827 @classmethod
4810 def load_provider_plugin(cls, plugin_id):
4828 def load_provider_plugin(cls, plugin_id):
4811 from rhodecode.authentication.base import loadplugin
4829 from rhodecode.authentication.base import loadplugin
4812 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4830 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4813 auth_plugin = loadplugin(_plugin_id)
4831 auth_plugin = loadplugin(_plugin_id)
4814 return auth_plugin
4832 return auth_plugin
4815
4833
4816
4834
4817 class Integration(Base, BaseModel):
4835 class Integration(Base, BaseModel):
4818 __tablename__ = 'integrations'
4836 __tablename__ = 'integrations'
4819 __table_args__ = (
4837 __table_args__ = (
4820 base_table_args
4838 base_table_args
4821 )
4839 )
4822
4840
4823 integration_id = Column('integration_id', Integer(), primary_key=True)
4841 integration_id = Column('integration_id', Integer(), primary_key=True)
4824 integration_type = Column('integration_type', String(255))
4842 integration_type = Column('integration_type', String(255))
4825 enabled = Column('enabled', Boolean(), nullable=False)
4843 enabled = Column('enabled', Boolean(), nullable=False)
4826 name = Column('name', String(255), nullable=False)
4844 name = Column('name', String(255), nullable=False)
4827 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4845 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4828 default=False)
4846 default=False)
4829
4847
4830 settings = Column(
4848 settings = Column(
4831 'settings_json', MutationObj.as_mutable(
4849 'settings_json', MutationObj.as_mutable(
4832 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4850 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4833 repo_id = Column(
4851 repo_id = Column(
4834 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4852 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4835 nullable=True, unique=None, default=None)
4853 nullable=True, unique=None, default=None)
4836 repo = relationship('Repository', lazy='joined')
4854 repo = relationship('Repository', lazy='joined')
4837
4855
4838 repo_group_id = Column(
4856 repo_group_id = Column(
4839 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4857 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4840 nullable=True, unique=None, default=None)
4858 nullable=True, unique=None, default=None)
4841 repo_group = relationship('RepoGroup', lazy='joined')
4859 repo_group = relationship('RepoGroup', lazy='joined')
4842
4860
4843 @property
4861 @property
4844 def scope(self):
4862 def scope(self):
4845 if self.repo:
4863 if self.repo:
4846 return repr(self.repo)
4864 return repr(self.repo)
4847 if self.repo_group:
4865 if self.repo_group:
4848 if self.child_repos_only:
4866 if self.child_repos_only:
4849 return repr(self.repo_group) + ' (child repos only)'
4867 return repr(self.repo_group) + ' (child repos only)'
4850 else:
4868 else:
4851 return repr(self.repo_group) + ' (recursive)'
4869 return repr(self.repo_group) + ' (recursive)'
4852 if self.child_repos_only:
4870 if self.child_repos_only:
4853 return 'root_repos'
4871 return 'root_repos'
4854 return 'global'
4872 return 'global'
4855
4873
4856 def __repr__(self):
4874 def __repr__(self):
4857 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4875 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4858
4876
4859
4877
4860 class RepoReviewRuleUser(Base, BaseModel):
4878 class RepoReviewRuleUser(Base, BaseModel):
4861 __tablename__ = 'repo_review_rules_users'
4879 __tablename__ = 'repo_review_rules_users'
4862 __table_args__ = (
4880 __table_args__ = (
4863 base_table_args
4881 base_table_args
4864 )
4882 )
4865
4883
4866 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4884 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4867 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4885 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4868 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4886 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4869 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4887 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4870 user = relationship('User')
4888 user = relationship('User')
4871
4889
4872 def rule_data(self):
4890 def rule_data(self):
4873 return {
4891 return {
4874 'mandatory': self.mandatory
4892 'mandatory': self.mandatory
4875 }
4893 }
4876
4894
4877
4895
4878 class RepoReviewRuleUserGroup(Base, BaseModel):
4896 class RepoReviewRuleUserGroup(Base, BaseModel):
4879 __tablename__ = 'repo_review_rules_users_groups'
4897 __tablename__ = 'repo_review_rules_users_groups'
4880 __table_args__ = (
4898 __table_args__ = (
4881 base_table_args
4899 base_table_args
4882 )
4900 )
4883
4901
4884 VOTE_RULE_ALL = -1
4902 VOTE_RULE_ALL = -1
4885
4903
4886 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4904 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4887 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4905 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4888 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4906 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4889 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4907 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4890 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4908 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4891 users_group = relationship('UserGroup')
4909 users_group = relationship('UserGroup')
4892
4910
4893 def rule_data(self):
4911 def rule_data(self):
4894 return {
4912 return {
4895 'mandatory': self.mandatory,
4913 'mandatory': self.mandatory,
4896 'vote_rule': self.vote_rule
4914 'vote_rule': self.vote_rule
4897 }
4915 }
4898
4916
4899 @property
4917 @property
4900 def vote_rule_label(self):
4918 def vote_rule_label(self):
4901 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4919 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4902 return 'all must vote'
4920 return 'all must vote'
4903 else:
4921 else:
4904 return 'min. vote {}'.format(self.vote_rule)
4922 return 'min. vote {}'.format(self.vote_rule)
4905
4923
4906
4924
4907 class RepoReviewRule(Base, BaseModel):
4925 class RepoReviewRule(Base, BaseModel):
4908 __tablename__ = 'repo_review_rules'
4926 __tablename__ = 'repo_review_rules'
4909 __table_args__ = (
4927 __table_args__ = (
4910 base_table_args
4928 base_table_args
4911 )
4929 )
4912
4930
4913 repo_review_rule_id = Column(
4931 repo_review_rule_id = Column(
4914 'repo_review_rule_id', Integer(), primary_key=True)
4932 'repo_review_rule_id', Integer(), primary_key=True)
4915 repo_id = Column(
4933 repo_id = Column(
4916 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4934 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4917 repo = relationship('Repository', backref='review_rules')
4935 repo = relationship('Repository', backref='review_rules')
4918
4936
4919 review_rule_name = Column('review_rule_name', String(255))
4937 review_rule_name = Column('review_rule_name', String(255))
4920 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4938 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4921 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4939 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4922 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4940 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4923
4941
4924 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4942 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4925 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4943 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4926 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4944 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4927 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4945 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4928
4946
4929 rule_users = relationship('RepoReviewRuleUser')
4947 rule_users = relationship('RepoReviewRuleUser')
4930 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4948 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4931
4949
4932 def _validate_pattern(self, value):
4950 def _validate_pattern(self, value):
4933 re.compile('^' + glob2re(value) + '$')
4951 re.compile('^' + glob2re(value) + '$')
4934
4952
4935 @hybrid_property
4953 @hybrid_property
4936 def source_branch_pattern(self):
4954 def source_branch_pattern(self):
4937 return self._branch_pattern or '*'
4955 return self._branch_pattern or '*'
4938
4956
4939 @source_branch_pattern.setter
4957 @source_branch_pattern.setter
4940 def source_branch_pattern(self, value):
4958 def source_branch_pattern(self, value):
4941 self._validate_pattern(value)
4959 self._validate_pattern(value)
4942 self._branch_pattern = value or '*'
4960 self._branch_pattern = value or '*'
4943
4961
4944 @hybrid_property
4962 @hybrid_property
4945 def target_branch_pattern(self):
4963 def target_branch_pattern(self):
4946 return self._target_branch_pattern or '*'
4964 return self._target_branch_pattern or '*'
4947
4965
4948 @target_branch_pattern.setter
4966 @target_branch_pattern.setter
4949 def target_branch_pattern(self, value):
4967 def target_branch_pattern(self, value):
4950 self._validate_pattern(value)
4968 self._validate_pattern(value)
4951 self._target_branch_pattern = value or '*'
4969 self._target_branch_pattern = value or '*'
4952
4970
4953 @hybrid_property
4971 @hybrid_property
4954 def file_pattern(self):
4972 def file_pattern(self):
4955 return self._file_pattern or '*'
4973 return self._file_pattern or '*'
4956
4974
4957 @file_pattern.setter
4975 @file_pattern.setter
4958 def file_pattern(self, value):
4976 def file_pattern(self, value):
4959 self._validate_pattern(value)
4977 self._validate_pattern(value)
4960 self._file_pattern = value or '*'
4978 self._file_pattern = value or '*'
4961
4979
4962 def matches(self, source_branch, target_branch, files_changed):
4980 def matches(self, source_branch, target_branch, files_changed):
4963 """
4981 """
4964 Check if this review rule matches a branch/files in a pull request
4982 Check if this review rule matches a branch/files in a pull request
4965
4983
4966 :param source_branch: source branch name for the commit
4984 :param source_branch: source branch name for the commit
4967 :param target_branch: target branch name for the commit
4985 :param target_branch: target branch name for the commit
4968 :param files_changed: list of file paths changed in the pull request
4986 :param files_changed: list of file paths changed in the pull request
4969 """
4987 """
4970
4988
4971 source_branch = source_branch or ''
4989 source_branch = source_branch or ''
4972 target_branch = target_branch or ''
4990 target_branch = target_branch or ''
4973 files_changed = files_changed or []
4991 files_changed = files_changed or []
4974
4992
4975 branch_matches = True
4993 branch_matches = True
4976 if source_branch or target_branch:
4994 if source_branch or target_branch:
4977 if self.source_branch_pattern == '*':
4995 if self.source_branch_pattern == '*':
4978 source_branch_match = True
4996 source_branch_match = True
4979 else:
4997 else:
4980 if self.source_branch_pattern.startswith('re:'):
4998 if self.source_branch_pattern.startswith('re:'):
4981 source_pattern = self.source_branch_pattern[3:]
4999 source_pattern = self.source_branch_pattern[3:]
4982 else:
5000 else:
4983 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5001 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4984 source_branch_regex = re.compile(source_pattern)
5002 source_branch_regex = re.compile(source_pattern)
4985 source_branch_match = bool(source_branch_regex.search(source_branch))
5003 source_branch_match = bool(source_branch_regex.search(source_branch))
4986 if self.target_branch_pattern == '*':
5004 if self.target_branch_pattern == '*':
4987 target_branch_match = True
5005 target_branch_match = True
4988 else:
5006 else:
4989 if self.target_branch_pattern.startswith('re:'):
5007 if self.target_branch_pattern.startswith('re:'):
4990 target_pattern = self.target_branch_pattern[3:]
5008 target_pattern = self.target_branch_pattern[3:]
4991 else:
5009 else:
4992 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5010 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4993 target_branch_regex = re.compile(target_pattern)
5011 target_branch_regex = re.compile(target_pattern)
4994 target_branch_match = bool(target_branch_regex.search(target_branch))
5012 target_branch_match = bool(target_branch_regex.search(target_branch))
4995
5013
4996 branch_matches = source_branch_match and target_branch_match
5014 branch_matches = source_branch_match and target_branch_match
4997
5015
4998 files_matches = True
5016 files_matches = True
4999 if self.file_pattern != '*':
5017 if self.file_pattern != '*':
5000 files_matches = False
5018 files_matches = False
5001 if self.file_pattern.startswith('re:'):
5019 if self.file_pattern.startswith('re:'):
5002 file_pattern = self.file_pattern[3:]
5020 file_pattern = self.file_pattern[3:]
5003 else:
5021 else:
5004 file_pattern = glob2re(self.file_pattern)
5022 file_pattern = glob2re(self.file_pattern)
5005 file_regex = re.compile(file_pattern)
5023 file_regex = re.compile(file_pattern)
5006 for file_data in files_changed:
5024 for file_data in files_changed:
5007 filename = file_data.get('filename')
5025 filename = file_data.get('filename')
5008
5026
5009 if file_regex.search(filename):
5027 if file_regex.search(filename):
5010 files_matches = True
5028 files_matches = True
5011 break
5029 break
5012
5030
5013 return branch_matches and files_matches
5031 return branch_matches and files_matches
5014
5032
5015 @property
5033 @property
5016 def review_users(self):
5034 def review_users(self):
5017 """ Returns the users which this rule applies to """
5035 """ Returns the users which this rule applies to """
5018
5036
5019 users = collections.OrderedDict()
5037 users = collections.OrderedDict()
5020
5038
5021 for rule_user in self.rule_users:
5039 for rule_user in self.rule_users:
5022 if rule_user.user.active:
5040 if rule_user.user.active:
5023 if rule_user.user not in users:
5041 if rule_user.user not in users:
5024 users[rule_user.user.username] = {
5042 users[rule_user.user.username] = {
5025 'user': rule_user.user,
5043 'user': rule_user.user,
5026 'source': 'user',
5044 'source': 'user',
5027 'source_data': {},
5045 'source_data': {},
5028 'data': rule_user.rule_data()
5046 'data': rule_user.rule_data()
5029 }
5047 }
5030
5048
5031 for rule_user_group in self.rule_user_groups:
5049 for rule_user_group in self.rule_user_groups:
5032 source_data = {
5050 source_data = {
5033 'user_group_id': rule_user_group.users_group.users_group_id,
5051 'user_group_id': rule_user_group.users_group.users_group_id,
5034 'name': rule_user_group.users_group.users_group_name,
5052 'name': rule_user_group.users_group.users_group_name,
5035 'members': len(rule_user_group.users_group.members)
5053 'members': len(rule_user_group.users_group.members)
5036 }
5054 }
5037 for member in rule_user_group.users_group.members:
5055 for member in rule_user_group.users_group.members:
5038 if member.user.active:
5056 if member.user.active:
5039 key = member.user.username
5057 key = member.user.username
5040 if key in users:
5058 if key in users:
5041 # skip this member as we have him already
5059 # skip this member as we have him already
5042 # this prevents from override the "first" matched
5060 # this prevents from override the "first" matched
5043 # users with duplicates in multiple groups
5061 # users with duplicates in multiple groups
5044 continue
5062 continue
5045
5063
5046 users[key] = {
5064 users[key] = {
5047 'user': member.user,
5065 'user': member.user,
5048 'source': 'user_group',
5066 'source': 'user_group',
5049 'source_data': source_data,
5067 'source_data': source_data,
5050 'data': rule_user_group.rule_data()
5068 'data': rule_user_group.rule_data()
5051 }
5069 }
5052
5070
5053 return users
5071 return users
5054
5072
5055 def user_group_vote_rule(self, user_id):
5073 def user_group_vote_rule(self, user_id):
5056
5074
5057 rules = []
5075 rules = []
5058 if not self.rule_user_groups:
5076 if not self.rule_user_groups:
5059 return rules
5077 return rules
5060
5078
5061 for user_group in self.rule_user_groups:
5079 for user_group in self.rule_user_groups:
5062 user_group_members = [x.user_id for x in user_group.users_group.members]
5080 user_group_members = [x.user_id for x in user_group.users_group.members]
5063 if user_id in user_group_members:
5081 if user_id in user_group_members:
5064 rules.append(user_group)
5082 rules.append(user_group)
5065 return rules
5083 return rules
5066
5084
5067 def __repr__(self):
5085 def __repr__(self):
5068 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5086 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5069 self.repo_review_rule_id, self.repo)
5087 self.repo_review_rule_id, self.repo)
5070
5088
5071
5089
5072 class ScheduleEntry(Base, BaseModel):
5090 class ScheduleEntry(Base, BaseModel):
5073 __tablename__ = 'schedule_entries'
5091 __tablename__ = 'schedule_entries'
5074 __table_args__ = (
5092 __table_args__ = (
5075 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5093 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5076 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5094 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5077 base_table_args,
5095 base_table_args,
5078 )
5096 )
5079
5097
5080 schedule_types = ['crontab', 'timedelta', 'integer']
5098 schedule_types = ['crontab', 'timedelta', 'integer']
5081 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5099 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5082
5100
5083 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5101 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5084 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5102 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5085 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5103 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5086
5104
5087 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5105 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5088 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5106 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5089
5107
5090 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5108 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5091 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5109 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5092
5110
5093 # task
5111 # task
5094 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5112 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5095 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5113 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5096 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5114 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5097 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5115 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5098
5116
5099 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5117 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5100 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5118 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5101
5119
5102 @hybrid_property
5120 @hybrid_property
5103 def schedule_type(self):
5121 def schedule_type(self):
5104 return self._schedule_type
5122 return self._schedule_type
5105
5123
5106 @schedule_type.setter
5124 @schedule_type.setter
5107 def schedule_type(self, val):
5125 def schedule_type(self, val):
5108 if val not in self.schedule_types:
5126 if val not in self.schedule_types:
5109 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5127 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5110 val, self.schedule_type))
5128 val, self.schedule_type))
5111
5129
5112 self._schedule_type = val
5130 self._schedule_type = val
5113
5131
5114 @classmethod
5132 @classmethod
5115 def get_uid(cls, obj):
5133 def get_uid(cls, obj):
5116 args = obj.task_args
5134 args = obj.task_args
5117 kwargs = obj.task_kwargs
5135 kwargs = obj.task_kwargs
5118 if isinstance(args, JsonRaw):
5136 if isinstance(args, JsonRaw):
5119 try:
5137 try:
5120 args = json.loads(args)
5138 args = json.loads(args)
5121 except ValueError:
5139 except ValueError:
5122 args = tuple()
5140 args = tuple()
5123
5141
5124 if isinstance(kwargs, JsonRaw):
5142 if isinstance(kwargs, JsonRaw):
5125 try:
5143 try:
5126 kwargs = json.loads(kwargs)
5144 kwargs = json.loads(kwargs)
5127 except ValueError:
5145 except ValueError:
5128 kwargs = dict()
5146 kwargs = dict()
5129
5147
5130 dot_notation = obj.task_dot_notation
5148 dot_notation = obj.task_dot_notation
5131 val = '.'.join(map(safe_str, [
5149 val = '.'.join(map(safe_str, [
5132 sorted(dot_notation), args, sorted(kwargs.items())]))
5150 sorted(dot_notation), args, sorted(kwargs.items())]))
5133 return hashlib.sha1(val).hexdigest()
5151 return hashlib.sha1(val).hexdigest()
5134
5152
5135 @classmethod
5153 @classmethod
5136 def get_by_schedule_name(cls, schedule_name):
5154 def get_by_schedule_name(cls, schedule_name):
5137 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5155 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5138
5156
5139 @classmethod
5157 @classmethod
5140 def get_by_schedule_id(cls, schedule_id):
5158 def get_by_schedule_id(cls, schedule_id):
5141 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5159 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5142
5160
5143 @property
5161 @property
5144 def task(self):
5162 def task(self):
5145 return self.task_dot_notation
5163 return self.task_dot_notation
5146
5164
5147 @property
5165 @property
5148 def schedule(self):
5166 def schedule(self):
5149 from rhodecode.lib.celerylib.utils import raw_2_schedule
5167 from rhodecode.lib.celerylib.utils import raw_2_schedule
5150 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5168 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5151 return schedule
5169 return schedule
5152
5170
5153 @property
5171 @property
5154 def args(self):
5172 def args(self):
5155 try:
5173 try:
5156 return list(self.task_args or [])
5174 return list(self.task_args or [])
5157 except ValueError:
5175 except ValueError:
5158 return list()
5176 return list()
5159
5177
5160 @property
5178 @property
5161 def kwargs(self):
5179 def kwargs(self):
5162 try:
5180 try:
5163 return dict(self.task_kwargs or {})
5181 return dict(self.task_kwargs or {})
5164 except ValueError:
5182 except ValueError:
5165 return dict()
5183 return dict()
5166
5184
5167 def _as_raw(self, val):
5185 def _as_raw(self, val):
5168 if hasattr(val, 'de_coerce'):
5186 if hasattr(val, 'de_coerce'):
5169 val = val.de_coerce()
5187 val = val.de_coerce()
5170 if val:
5188 if val:
5171 val = json.dumps(val)
5189 val = json.dumps(val)
5172
5190
5173 return val
5191 return val
5174
5192
5175 @property
5193 @property
5176 def schedule_definition_raw(self):
5194 def schedule_definition_raw(self):
5177 return self._as_raw(self.schedule_definition)
5195 return self._as_raw(self.schedule_definition)
5178
5196
5179 @property
5197 @property
5180 def args_raw(self):
5198 def args_raw(self):
5181 return self._as_raw(self.task_args)
5199 return self._as_raw(self.task_args)
5182
5200
5183 @property
5201 @property
5184 def kwargs_raw(self):
5202 def kwargs_raw(self):
5185 return self._as_raw(self.task_kwargs)
5203 return self._as_raw(self.task_kwargs)
5186
5204
5187 def __repr__(self):
5205 def __repr__(self):
5188 return '<DB:ScheduleEntry({}:{})>'.format(
5206 return '<DB:ScheduleEntry({}:{})>'.format(
5189 self.schedule_entry_id, self.schedule_name)
5207 self.schedule_entry_id, self.schedule_name)
5190
5208
5191
5209
5192 @event.listens_for(ScheduleEntry, 'before_update')
5210 @event.listens_for(ScheduleEntry, 'before_update')
5193 def update_task_uid(mapper, connection, target):
5211 def update_task_uid(mapper, connection, target):
5194 target.task_uid = ScheduleEntry.get_uid(target)
5212 target.task_uid = ScheduleEntry.get_uid(target)
5195
5213
5196
5214
5197 @event.listens_for(ScheduleEntry, 'before_insert')
5215 @event.listens_for(ScheduleEntry, 'before_insert')
5198 def set_task_uid(mapper, connection, target):
5216 def set_task_uid(mapper, connection, target):
5199 target.task_uid = ScheduleEntry.get_uid(target)
5217 target.task_uid = ScheduleEntry.get_uid(target)
5200
5218
5201
5219
5202 class _BaseBranchPerms(BaseModel):
5220 class _BaseBranchPerms(BaseModel):
5203 @classmethod
5221 @classmethod
5204 def compute_hash(cls, value):
5222 def compute_hash(cls, value):
5205 return sha1_safe(value)
5223 return sha1_safe(value)
5206
5224
5207 @hybrid_property
5225 @hybrid_property
5208 def branch_pattern(self):
5226 def branch_pattern(self):
5209 return self._branch_pattern or '*'
5227 return self._branch_pattern or '*'
5210
5228
5211 @hybrid_property
5229 @hybrid_property
5212 def branch_hash(self):
5230 def branch_hash(self):
5213 return self._branch_hash
5231 return self._branch_hash
5214
5232
5215 def _validate_glob(self, value):
5233 def _validate_glob(self, value):
5216 re.compile('^' + glob2re(value) + '$')
5234 re.compile('^' + glob2re(value) + '$')
5217
5235
5218 @branch_pattern.setter
5236 @branch_pattern.setter
5219 def branch_pattern(self, value):
5237 def branch_pattern(self, value):
5220 self._validate_glob(value)
5238 self._validate_glob(value)
5221 self._branch_pattern = value or '*'
5239 self._branch_pattern = value or '*'
5222 # set the Hash when setting the branch pattern
5240 # set the Hash when setting the branch pattern
5223 self._branch_hash = self.compute_hash(self._branch_pattern)
5241 self._branch_hash = self.compute_hash(self._branch_pattern)
5224
5242
5225 def matches(self, branch):
5243 def matches(self, branch):
5226 """
5244 """
5227 Check if this the branch matches entry
5245 Check if this the branch matches entry
5228
5246
5229 :param branch: branch name for the commit
5247 :param branch: branch name for the commit
5230 """
5248 """
5231
5249
5232 branch = branch or ''
5250 branch = branch or ''
5233
5251
5234 branch_matches = True
5252 branch_matches = True
5235 if branch:
5253 if branch:
5236 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5254 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5237 branch_matches = bool(branch_regex.search(branch))
5255 branch_matches = bool(branch_regex.search(branch))
5238
5256
5239 return branch_matches
5257 return branch_matches
5240
5258
5241
5259
5242 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5260 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5243 __tablename__ = 'user_to_repo_branch_permissions'
5261 __tablename__ = 'user_to_repo_branch_permissions'
5244 __table_args__ = (
5262 __table_args__ = (
5245 base_table_args
5263 base_table_args
5246 )
5264 )
5247
5265
5248 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5266 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5249
5267
5250 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5268 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5251 repo = relationship('Repository', backref='user_branch_perms')
5269 repo = relationship('Repository', backref='user_branch_perms')
5252
5270
5253 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5271 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5254 permission = relationship('Permission')
5272 permission = relationship('Permission')
5255
5273
5256 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5274 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5257 user_repo_to_perm = relationship('UserRepoToPerm')
5275 user_repo_to_perm = relationship('UserRepoToPerm')
5258
5276
5259 rule_order = Column('rule_order', Integer(), nullable=False)
5277 rule_order = Column('rule_order', Integer(), nullable=False)
5260 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5278 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5261 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5279 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5262
5280
5263 def __unicode__(self):
5281 def __unicode__(self):
5264 return u'<UserBranchPermission(%s => %r)>' % (
5282 return u'<UserBranchPermission(%s => %r)>' % (
5265 self.user_repo_to_perm, self.branch_pattern)
5283 self.user_repo_to_perm, self.branch_pattern)
5266
5284
5267
5285
5268 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5286 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5269 __tablename__ = 'user_group_to_repo_branch_permissions'
5287 __tablename__ = 'user_group_to_repo_branch_permissions'
5270 __table_args__ = (
5288 __table_args__ = (
5271 base_table_args
5289 base_table_args
5272 )
5290 )
5273
5291
5274 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5292 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5275
5293
5276 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5294 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5277 repo = relationship('Repository', backref='user_group_branch_perms')
5295 repo = relationship('Repository', backref='user_group_branch_perms')
5278
5296
5279 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5297 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5280 permission = relationship('Permission')
5298 permission = relationship('Permission')
5281
5299
5282 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)
5300 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)
5283 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5301 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5284
5302
5285 rule_order = Column('rule_order', Integer(), nullable=False)
5303 rule_order = Column('rule_order', Integer(), nullable=False)
5286 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5304 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5287 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5305 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5288
5306
5289 def __unicode__(self):
5307 def __unicode__(self):
5290 return u'<UserBranchPermission(%s => %r)>' % (
5308 return u'<UserBranchPermission(%s => %r)>' % (
5291 self.user_group_repo_to_perm, self.branch_pattern)
5309 self.user_group_repo_to_perm, self.branch_pattern)
5292
5310
5293
5311
5294 class UserBookmark(Base, BaseModel):
5312 class UserBookmark(Base, BaseModel):
5295 __tablename__ = 'user_bookmarks'
5313 __tablename__ = 'user_bookmarks'
5296 __table_args__ = (
5314 __table_args__ = (
5297 UniqueConstraint('user_id', 'bookmark_repo_id'),
5315 UniqueConstraint('user_id', 'bookmark_repo_id'),
5298 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5316 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5299 UniqueConstraint('user_id', 'bookmark_position'),
5317 UniqueConstraint('user_id', 'bookmark_position'),
5300 base_table_args
5318 base_table_args
5301 )
5319 )
5302
5320
5303 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5321 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5322 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5305 position = Column("bookmark_position", Integer(), nullable=False)
5323 position = Column("bookmark_position", Integer(), nullable=False)
5306 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5324 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5307 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5325 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5308 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5326 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5309
5327
5310 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5328 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5311 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5329 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5312
5330
5313 user = relationship("User")
5331 user = relationship("User")
5314
5332
5315 repository = relationship("Repository")
5333 repository = relationship("Repository")
5316 repository_group = relationship("RepoGroup")
5334 repository_group = relationship("RepoGroup")
5317
5335
5318 @classmethod
5336 @classmethod
5319 def get_by_position_for_user(cls, position, user_id):
5337 def get_by_position_for_user(cls, position, user_id):
5320 return cls.query() \
5338 return cls.query() \
5321 .filter(UserBookmark.user_id == user_id) \
5339 .filter(UserBookmark.user_id == user_id) \
5322 .filter(UserBookmark.position == position).scalar()
5340 .filter(UserBookmark.position == position).scalar()
5323
5341
5324 @classmethod
5342 @classmethod
5325 def get_bookmarks_for_user(cls, user_id, cache=True):
5343 def get_bookmarks_for_user(cls, user_id, cache=True):
5326 bookmarks = cls.query() \
5344 bookmarks = cls.query() \
5327 .filter(UserBookmark.user_id == user_id) \
5345 .filter(UserBookmark.user_id == user_id) \
5328 .options(joinedload(UserBookmark.repository)) \
5346 .options(joinedload(UserBookmark.repository)) \
5329 .options(joinedload(UserBookmark.repository_group)) \
5347 .options(joinedload(UserBookmark.repository_group)) \
5330 .order_by(UserBookmark.position.asc())
5348 .order_by(UserBookmark.position.asc())
5331
5349
5332 if cache:
5350 if cache:
5333 bookmarks = bookmarks.options(
5351 bookmarks = bookmarks.options(
5334 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5352 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5335 )
5353 )
5336
5354
5337 return bookmarks.all()
5355 return bookmarks.all()
5338
5356
5339 def __unicode__(self):
5357 def __unicode__(self):
5340 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5358 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5341
5359
5342
5360
5343 class FileStore(Base, BaseModel):
5361 class FileStore(Base, BaseModel):
5344 __tablename__ = 'file_store'
5362 __tablename__ = 'file_store'
5345 __table_args__ = (
5363 __table_args__ = (
5346 base_table_args
5364 base_table_args
5347 )
5365 )
5348
5366
5349 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5367 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5350 file_uid = Column('file_uid', String(1024), nullable=False)
5368 file_uid = Column('file_uid', String(1024), nullable=False)
5351 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5369 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5352 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5370 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5353 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5371 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5354
5372
5355 # sha256 hash
5373 # sha256 hash
5356 file_hash = Column('file_hash', String(512), nullable=False)
5374 file_hash = Column('file_hash', String(512), nullable=False)
5357 file_size = Column('file_size', BigInteger(), nullable=False)
5375 file_size = Column('file_size', BigInteger(), nullable=False)
5358
5376
5359 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5377 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5360 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5378 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5361 accessed_count = Column('accessed_count', Integer(), default=0)
5379 accessed_count = Column('accessed_count', Integer(), default=0)
5362
5380
5363 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5381 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5364
5382
5365 # if repo/repo_group reference is set, check for permissions
5383 # if repo/repo_group reference is set, check for permissions
5366 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5384 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5367
5385
5368 # hidden defines an attachment that should be hidden from showing in artifact listing
5386 # hidden defines an attachment that should be hidden from showing in artifact listing
5369 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5387 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5370
5388
5371 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5389 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5372 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5390 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5373
5391
5374 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5392 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5375
5393
5376 # scope limited to user, which requester have access to
5394 # scope limited to user, which requester have access to
5377 scope_user_id = Column(
5395 scope_user_id = Column(
5378 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5396 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5379 nullable=True, unique=None, default=None)
5397 nullable=True, unique=None, default=None)
5380 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5398 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5381
5399
5382 # scope limited to user group, which requester have access to
5400 # scope limited to user group, which requester have access to
5383 scope_user_group_id = Column(
5401 scope_user_group_id = Column(
5384 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5402 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5385 nullable=True, unique=None, default=None)
5403 nullable=True, unique=None, default=None)
5386 user_group = relationship('UserGroup', lazy='joined')
5404 user_group = relationship('UserGroup', lazy='joined')
5387
5405
5388 # scope limited to repo, which requester have access to
5406 # scope limited to repo, which requester have access to
5389 scope_repo_id = Column(
5407 scope_repo_id = Column(
5390 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5408 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5391 nullable=True, unique=None, default=None)
5409 nullable=True, unique=None, default=None)
5392 repo = relationship('Repository', lazy='joined')
5410 repo = relationship('Repository', lazy='joined')
5393
5411
5394 # scope limited to repo group, which requester have access to
5412 # scope limited to repo group, which requester have access to
5395 scope_repo_group_id = Column(
5413 scope_repo_group_id = Column(
5396 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5414 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5397 nullable=True, unique=None, default=None)
5415 nullable=True, unique=None, default=None)
5398 repo_group = relationship('RepoGroup', lazy='joined')
5416 repo_group = relationship('RepoGroup', lazy='joined')
5399
5417
5400 @classmethod
5418 @classmethod
5401 def get_by_store_uid(cls, file_store_uid):
5419 def get_by_store_uid(cls, file_store_uid):
5402 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5420 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5403
5421
5404 @classmethod
5422 @classmethod
5405 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5423 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5406 file_description='', enabled=True, hidden=False, check_acl=True,
5424 file_description='', enabled=True, hidden=False, check_acl=True,
5407 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5425 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5408
5426
5409 store_entry = FileStore()
5427 store_entry = FileStore()
5410 store_entry.file_uid = file_uid
5428 store_entry.file_uid = file_uid
5411 store_entry.file_display_name = file_display_name
5429 store_entry.file_display_name = file_display_name
5412 store_entry.file_org_name = filename
5430 store_entry.file_org_name = filename
5413 store_entry.file_size = file_size
5431 store_entry.file_size = file_size
5414 store_entry.file_hash = file_hash
5432 store_entry.file_hash = file_hash
5415 store_entry.file_description = file_description
5433 store_entry.file_description = file_description
5416
5434
5417 store_entry.check_acl = check_acl
5435 store_entry.check_acl = check_acl
5418 store_entry.enabled = enabled
5436 store_entry.enabled = enabled
5419 store_entry.hidden = hidden
5437 store_entry.hidden = hidden
5420
5438
5421 store_entry.user_id = user_id
5439 store_entry.user_id = user_id
5422 store_entry.scope_user_id = scope_user_id
5440 store_entry.scope_user_id = scope_user_id
5423 store_entry.scope_repo_id = scope_repo_id
5441 store_entry.scope_repo_id = scope_repo_id
5424 store_entry.scope_repo_group_id = scope_repo_group_id
5442 store_entry.scope_repo_group_id = scope_repo_group_id
5425
5443
5426 return store_entry
5444 return store_entry
5427
5445
5428 @classmethod
5446 @classmethod
5429 def store_metadata(cls, file_store_id, args, commit=True):
5447 def store_metadata(cls, file_store_id, args, commit=True):
5430 file_store = FileStore.get(file_store_id)
5448 file_store = FileStore.get(file_store_id)
5431 if file_store is None:
5449 if file_store is None:
5432 return
5450 return
5433
5451
5434 for section, key, value, value_type in args:
5452 for section, key, value, value_type in args:
5435 has_key = FileStoreMetadata().query() \
5453 has_key = FileStoreMetadata().query() \
5436 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5454 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5437 .filter(FileStoreMetadata.file_store_meta_section == section) \
5455 .filter(FileStoreMetadata.file_store_meta_section == section) \
5438 .filter(FileStoreMetadata.file_store_meta_key == key) \
5456 .filter(FileStoreMetadata.file_store_meta_key == key) \
5439 .scalar()
5457 .scalar()
5440 if has_key:
5458 if has_key:
5441 msg = 'key `{}` already defined under section `{}` for this file.'\
5459 msg = 'key `{}` already defined under section `{}` for this file.'\
5442 .format(key, section)
5460 .format(key, section)
5443 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5461 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5444
5462
5445 # NOTE(marcink): raises ArtifactMetadataBadValueType
5463 # NOTE(marcink): raises ArtifactMetadataBadValueType
5446 FileStoreMetadata.valid_value_type(value_type)
5464 FileStoreMetadata.valid_value_type(value_type)
5447
5465
5448 meta_entry = FileStoreMetadata()
5466 meta_entry = FileStoreMetadata()
5449 meta_entry.file_store = file_store
5467 meta_entry.file_store = file_store
5450 meta_entry.file_store_meta_section = section
5468 meta_entry.file_store_meta_section = section
5451 meta_entry.file_store_meta_key = key
5469 meta_entry.file_store_meta_key = key
5452 meta_entry.file_store_meta_value_type = value_type
5470 meta_entry.file_store_meta_value_type = value_type
5453 meta_entry.file_store_meta_value = value
5471 meta_entry.file_store_meta_value = value
5454
5472
5455 Session().add(meta_entry)
5473 Session().add(meta_entry)
5456
5474
5457 try:
5475 try:
5458 if commit:
5476 if commit:
5459 Session().commit()
5477 Session().commit()
5460 except IntegrityError:
5478 except IntegrityError:
5461 Session().rollback()
5479 Session().rollback()
5462 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5480 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5463
5481
5464 @classmethod
5482 @classmethod
5465 def bump_access_counter(cls, file_uid, commit=True):
5483 def bump_access_counter(cls, file_uid, commit=True):
5466 FileStore().query()\
5484 FileStore().query()\
5467 .filter(FileStore.file_uid == file_uid)\
5485 .filter(FileStore.file_uid == file_uid)\
5468 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5486 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5469 FileStore.accessed_on: datetime.datetime.now()})
5487 FileStore.accessed_on: datetime.datetime.now()})
5470 if commit:
5488 if commit:
5471 Session().commit()
5489 Session().commit()
5472
5490
5473 def __json__(self):
5491 def __json__(self):
5474 data = {
5492 data = {
5475 'filename': self.file_display_name,
5493 'filename': self.file_display_name,
5476 'filename_org': self.file_org_name,
5494 'filename_org': self.file_org_name,
5477 'file_uid': self.file_uid,
5495 'file_uid': self.file_uid,
5478 'description': self.file_description,
5496 'description': self.file_description,
5479 'hidden': self.hidden,
5497 'hidden': self.hidden,
5480 'size': self.file_size,
5498 'size': self.file_size,
5481 'created_on': self.created_on,
5499 'created_on': self.created_on,
5482 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5500 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5483 'downloaded_times': self.accessed_count,
5501 'downloaded_times': self.accessed_count,
5484 'sha256': self.file_hash,
5502 'sha256': self.file_hash,
5485 'metadata': self.file_metadata,
5503 'metadata': self.file_metadata,
5486 }
5504 }
5487
5505
5488 return data
5506 return data
5489
5507
5490 def __repr__(self):
5508 def __repr__(self):
5491 return '<FileStore({})>'.format(self.file_store_id)
5509 return '<FileStore({})>'.format(self.file_store_id)
5492
5510
5493
5511
5494 class FileStoreMetadata(Base, BaseModel):
5512 class FileStoreMetadata(Base, BaseModel):
5495 __tablename__ = 'file_store_metadata'
5513 __tablename__ = 'file_store_metadata'
5496 __table_args__ = (
5514 __table_args__ = (
5497 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5515 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5498 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5516 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5499 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5517 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5500 base_table_args
5518 base_table_args
5501 )
5519 )
5502 SETTINGS_TYPES = {
5520 SETTINGS_TYPES = {
5503 'str': safe_str,
5521 'str': safe_str,
5504 'int': safe_int,
5522 'int': safe_int,
5505 'unicode': safe_unicode,
5523 'unicode': safe_unicode,
5506 'bool': str2bool,
5524 'bool': str2bool,
5507 'list': functools.partial(aslist, sep=',')
5525 'list': functools.partial(aslist, sep=',')
5508 }
5526 }
5509
5527
5510 file_store_meta_id = Column(
5528 file_store_meta_id = Column(
5511 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5529 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5512 primary_key=True)
5530 primary_key=True)
5513 _file_store_meta_section = Column(
5531 _file_store_meta_section = Column(
5514 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5532 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5515 nullable=True, unique=None, default=None)
5533 nullable=True, unique=None, default=None)
5516 _file_store_meta_section_hash = Column(
5534 _file_store_meta_section_hash = Column(
5517 "file_store_meta_section_hash", String(255),
5535 "file_store_meta_section_hash", String(255),
5518 nullable=True, unique=None, default=None)
5536 nullable=True, unique=None, default=None)
5519 _file_store_meta_key = Column(
5537 _file_store_meta_key = Column(
5520 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5538 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5521 nullable=True, unique=None, default=None)
5539 nullable=True, unique=None, default=None)
5522 _file_store_meta_key_hash = Column(
5540 _file_store_meta_key_hash = Column(
5523 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5541 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5524 _file_store_meta_value = Column(
5542 _file_store_meta_value = Column(
5525 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5543 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5526 nullable=True, unique=None, default=None)
5544 nullable=True, unique=None, default=None)
5527 _file_store_meta_value_type = Column(
5545 _file_store_meta_value_type = Column(
5528 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5546 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5529 default='unicode')
5547 default='unicode')
5530
5548
5531 file_store_id = Column(
5549 file_store_id = Column(
5532 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5550 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5533 nullable=True, unique=None, default=None)
5551 nullable=True, unique=None, default=None)
5534
5552
5535 file_store = relationship('FileStore', lazy='joined')
5553 file_store = relationship('FileStore', lazy='joined')
5536
5554
5537 @classmethod
5555 @classmethod
5538 def valid_value_type(cls, value):
5556 def valid_value_type(cls, value):
5539 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5557 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5540 raise ArtifactMetadataBadValueType(
5558 raise ArtifactMetadataBadValueType(
5541 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5559 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5542
5560
5543 @hybrid_property
5561 @hybrid_property
5544 def file_store_meta_section(self):
5562 def file_store_meta_section(self):
5545 return self._file_store_meta_section
5563 return self._file_store_meta_section
5546
5564
5547 @file_store_meta_section.setter
5565 @file_store_meta_section.setter
5548 def file_store_meta_section(self, value):
5566 def file_store_meta_section(self, value):
5549 self._file_store_meta_section = value
5567 self._file_store_meta_section = value
5550 self._file_store_meta_section_hash = _hash_key(value)
5568 self._file_store_meta_section_hash = _hash_key(value)
5551
5569
5552 @hybrid_property
5570 @hybrid_property
5553 def file_store_meta_key(self):
5571 def file_store_meta_key(self):
5554 return self._file_store_meta_key
5572 return self._file_store_meta_key
5555
5573
5556 @file_store_meta_key.setter
5574 @file_store_meta_key.setter
5557 def file_store_meta_key(self, value):
5575 def file_store_meta_key(self, value):
5558 self._file_store_meta_key = value
5576 self._file_store_meta_key = value
5559 self._file_store_meta_key_hash = _hash_key(value)
5577 self._file_store_meta_key_hash = _hash_key(value)
5560
5578
5561 @hybrid_property
5579 @hybrid_property
5562 def file_store_meta_value(self):
5580 def file_store_meta_value(self):
5563 val = self._file_store_meta_value
5581 val = self._file_store_meta_value
5564
5582
5565 if self._file_store_meta_value_type:
5583 if self._file_store_meta_value_type:
5566 # e.g unicode.encrypted == unicode
5584 # e.g unicode.encrypted == unicode
5567 _type = self._file_store_meta_value_type.split('.')[0]
5585 _type = self._file_store_meta_value_type.split('.')[0]
5568 # decode the encrypted value if it's encrypted field type
5586 # decode the encrypted value if it's encrypted field type
5569 if '.encrypted' in self._file_store_meta_value_type:
5587 if '.encrypted' in self._file_store_meta_value_type:
5570 cipher = EncryptedTextValue()
5588 cipher = EncryptedTextValue()
5571 val = safe_unicode(cipher.process_result_value(val, None))
5589 val = safe_unicode(cipher.process_result_value(val, None))
5572 # do final type conversion
5590 # do final type conversion
5573 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5591 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5574 val = converter(val)
5592 val = converter(val)
5575
5593
5576 return val
5594 return val
5577
5595
5578 @file_store_meta_value.setter
5596 @file_store_meta_value.setter
5579 def file_store_meta_value(self, val):
5597 def file_store_meta_value(self, val):
5580 val = safe_unicode(val)
5598 val = safe_unicode(val)
5581 # encode the encrypted value
5599 # encode the encrypted value
5582 if '.encrypted' in self.file_store_meta_value_type:
5600 if '.encrypted' in self.file_store_meta_value_type:
5583 cipher = EncryptedTextValue()
5601 cipher = EncryptedTextValue()
5584 val = safe_unicode(cipher.process_bind_param(val, None))
5602 val = safe_unicode(cipher.process_bind_param(val, None))
5585 self._file_store_meta_value = val
5603 self._file_store_meta_value = val
5586
5604
5587 @hybrid_property
5605 @hybrid_property
5588 def file_store_meta_value_type(self):
5606 def file_store_meta_value_type(self):
5589 return self._file_store_meta_value_type
5607 return self._file_store_meta_value_type
5590
5608
5591 @file_store_meta_value_type.setter
5609 @file_store_meta_value_type.setter
5592 def file_store_meta_value_type(self, val):
5610 def file_store_meta_value_type(self, val):
5593 # e.g unicode.encrypted
5611 # e.g unicode.encrypted
5594 self.valid_value_type(val)
5612 self.valid_value_type(val)
5595 self._file_store_meta_value_type = val
5613 self._file_store_meta_value_type = val
5596
5614
5597 def __json__(self):
5615 def __json__(self):
5598 data = {
5616 data = {
5599 'artifact': self.file_store.file_uid,
5617 'artifact': self.file_store.file_uid,
5600 'section': self.file_store_meta_section,
5618 'section': self.file_store_meta_section,
5601 'key': self.file_store_meta_key,
5619 'key': self.file_store_meta_key,
5602 'value': self.file_store_meta_value,
5620 'value': self.file_store_meta_value,
5603 }
5621 }
5604
5622
5605 return data
5623 return data
5606
5624
5607 def __repr__(self):
5625 def __repr__(self):
5608 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5626 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5609 self.file_store_meta_key, self.file_store_meta_value)
5627 self.file_store_meta_key, self.file_store_meta_value)
5610
5628
5611
5629
5612 class DbMigrateVersion(Base, BaseModel):
5630 class DbMigrateVersion(Base, BaseModel):
5613 __tablename__ = 'db_migrate_version'
5631 __tablename__ = 'db_migrate_version'
5614 __table_args__ = (
5632 __table_args__ = (
5615 base_table_args,
5633 base_table_args,
5616 )
5634 )
5617
5635
5618 repository_id = Column('repository_id', String(250), primary_key=True)
5636 repository_id = Column('repository_id', String(250), primary_key=True)
5619 repository_path = Column('repository_path', Text)
5637 repository_path = Column('repository_path', Text)
5620 version = Column('version', Integer)
5638 version = Column('version', Integer)
5621
5639
5622 @classmethod
5640 @classmethod
5623 def set_version(cls, version):
5641 def set_version(cls, version):
5624 """
5642 """
5625 Helper for forcing a different version, usually for debugging purposes via ishell.
5643 Helper for forcing a different version, usually for debugging purposes via ishell.
5626 """
5644 """
5627 ver = DbMigrateVersion.query().first()
5645 ver = DbMigrateVersion.query().first()
5628 ver.version = version
5646 ver.version = version
5629 Session().commit()
5647 Session().commit()
5630
5648
5631
5649
5632 class DbSession(Base, BaseModel):
5650 class DbSession(Base, BaseModel):
5633 __tablename__ = 'db_session'
5651 __tablename__ = 'db_session'
5634 __table_args__ = (
5652 __table_args__ = (
5635 base_table_args,
5653 base_table_args,
5636 )
5654 )
5637
5655
5638 def __repr__(self):
5656 def __repr__(self):
5639 return '<DB:DbSession({})>'.format(self.id)
5657 return '<DB:DbSession({})>'.format(self.id)
5640
5658
5641 id = Column('id', Integer())
5659 id = Column('id', Integer())
5642 namespace = Column('namespace', String(255), primary_key=True)
5660 namespace = Column('namespace', String(255), primary_key=True)
5643 accessed = Column('accessed', DateTime, nullable=False)
5661 accessed = Column('accessed', DateTime, nullable=False)
5644 created = Column('created', DateTime, nullable=False)
5662 created = Column('created', DateTime, nullable=False)
5645 data = Column('data', PickleType, nullable=False)
5663 data = Column('data', PickleType, nullable=False)
@@ -1,199 +1,205 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <script>
2 <script>
3 var showAuthToken = function(authTokenId) {
3 var showAuthToken = function(authTokenId) {
4 return _showAuthToken(authTokenId, pyroutes.url('my_account_auth_tokens_view'))
4 return _showAuthToken(authTokenId, pyroutes.url('my_account_auth_tokens_view'))
5 }
5 }
6 </script>
6 </script>
7
7
8 <div class="panel-heading">
8 <div class="panel-heading">
9 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
9 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
10 </div>
10 </div>
11 <div class="panel-body">
11 <div class="panel-body">
12 <div class="apikeys_wrap">
12 <div class="apikeys_wrap">
13 <p>
13 <p>
14 ${_('Authentication tokens can be used to interact with the API, or VCS-over-http. '
14 ${_('Available roles')}:
15 'Each token can have a role. Token with a role can be used only in given context, '
15 <ul>
16 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
16 % for role in h.UserApiKeys.ROLES:
17 <li>
18 <span class="tag disabled">${h.UserApiKeys._get_role_name(role)}</span>
19 <span>${h.UserApiKeys._get_role_description(role) |n}</span>
20 </li>
21 % endfor
22 </ul>
17 </p>
23 </p>
18 <table class="rctable auth_tokens">
24 <table class="rctable auth_tokens">
19 <tr>
25 <tr>
20 <th>${_('Token')}</th>
26 <th>${_('Token')}</th>
21 <th>${_('Description')}</th>
27 <th>${_('Description')}</th>
22 <th>${_('Role')}</th>
28 <th>${_('Role')}</th>
23 <th>${_('Repository Scope')}</th>
29 <th>${_('Repository Scope')}</th>
24 <th>${_('Expiration')}</th>
30 <th>${_('Expiration')}</th>
25 <th>${_('Action')}</th>
31 <th>${_('Action')}</th>
26 </tr>
32 </tr>
27 %if c.user_auth_tokens:
33 %if c.user_auth_tokens:
28 %for auth_token in c.user_auth_tokens:
34 %for auth_token in c.user_auth_tokens:
29 <tr class="${('expired' if auth_token.expired else '')}">
35 <tr class="${('expired' if auth_token.expired else '')}">
30 <td class="td-authtoken">
36 <td class="td-authtoken">
31 <div class="user_auth_tokens">
37 <div class="user_auth_tokens">
32 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
38 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
33 ${auth_token.token_obfuscated}
39 ${auth_token.token_obfuscated}
34 </code>
40 </code>
35 </div>
41 </div>
36 </td>
42 </td>
37 <td class="td-wrap">${auth_token.description}</td>
43 <td class="td-wrap">${auth_token.description}</td>
38 <td class="td-tags">
44 <td class="td-tags">
39 <span class="tag disabled">${auth_token.role_humanized}</span>
45 <span class="tooltip tag disabled" title="${h.UserApiKeys._get_role_description(auth_token.role)}">${auth_token.role_humanized}</span>
40 </td>
46 </td>
41 <td class="td">${auth_token.scope_humanized}</td>
47 <td class="td">${auth_token.scope_humanized}</td>
42 <td class="td-exp">
48 <td class="td-exp">
43 %if auth_token.expires == -1:
49 %if auth_token.expires == -1:
44 ${_('never')}
50 ${_('never')}
45 %else:
51 %else:
46 %if auth_token.expired:
52 %if auth_token.expired:
47 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
53 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
48 %else:
54 %else:
49 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
55 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
50 %endif
56 %endif
51 %endif
57 %endif
52 </td>
58 </td>
53 <td class="td-action">
59 <td class="td-action">
54 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
60 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
55 <input name="del_auth_token" type="hidden" value="${auth_token.user_api_key_id}">
61 <input name="del_auth_token" type="hidden" value="${auth_token.user_api_key_id}">
56 <button class="btn btn-link btn-danger" type="submit"
62 <button class="btn btn-link btn-danger" type="submit"
57 onclick="submitConfirm(event, this, _gettext('Confirm to delete this auth token'), _gettext('Delete'), '${auth_token.token_obfuscated}')"
63 onclick="submitConfirm(event, this, _gettext('Confirm to delete this auth token'), _gettext('Delete'), '${auth_token.token_obfuscated}')"
58 >
64 >
59 ${_('Delete')}
65 ${_('Delete')}
60 </button>
66 </button>
61 ${h.end_form()}
67 ${h.end_form()}
62 </td>
68 </td>
63 </tr>
69 </tr>
64 %endfor
70 %endfor
65 %else:
71 %else:
66 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
72 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
67 %endif
73 %endif
68 </table>
74 </table>
69 </div>
75 </div>
70
76
71 <div class="user_auth_tokens">
77 <div class="user_auth_tokens">
72 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)}
78 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)}
73 <div class="form form-vertical">
79 <div class="form form-vertical">
74 <!-- fields -->
80 <!-- fields -->
75 <div class="fields">
81 <div class="fields">
76 <div class="field">
82 <div class="field">
77 <div class="label">
83 <div class="label">
78 <label for="new_email">${_('New authentication token')}:</label>
84 <label for="new_email">${_('New authentication token')}:</label>
79 </div>
85 </div>
80 <div class="input">
86 <div class="input">
81 ${h.text('description', class_='medium', placeholder=_('Description'))}
87 ${h.text('description', class_='medium', placeholder=_('Description'))}
82 ${h.hidden('lifetime')}
88 ${h.hidden('lifetime')}
83 ${h.select('role', request.GET.get('token_role', ''), c.role_options)}
89 ${h.select('role', request.GET.get('token_role', ''), c.role_options)}
84
90
85 % if c.allow_scoped_tokens:
91 % if c.allow_scoped_tokens:
86 ${h.hidden('scope_repo_id')}
92 ${h.hidden('scope_repo_id')}
87 % else:
93 % else:
88 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
94 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
89 % endif
95 % endif
90 </div>
96 </div>
91 <p class="help-block">
97 <p class="help-block">
92 ${_('Repository scope works only with tokens with VCS type.')}
98 ${_('Repository scope works only with tokens with VCS type.')}
93 </p>
99 </p>
94 </div>
100 </div>
95 <div class="buttons">
101 <div class="buttons">
96 ${h.submit('save',_('Add'),class_="btn")}
102 ${h.submit('save',_('Add'),class_="btn")}
97 ${h.reset('reset',_('Reset'),class_="btn")}
103 ${h.reset('reset',_('Reset'),class_="btn")}
98 </div>
104 </div>
99 </div>
105 </div>
100 </div>
106 </div>
101 ${h.end_form()}
107 ${h.end_form()}
102 </div>
108 </div>
103 </div>
109 </div>
104 </div>
110 </div>
105
111
106 <script>
112 <script>
107 $(document).ready(function(){
113 $(document).ready(function(){
108
114
109 var select2Options = {
115 var select2Options = {
110 'containerCssClass': "drop-menu",
116 'containerCssClass': "drop-menu",
111 'dropdownCssClass': "drop-menu-dropdown",
117 'dropdownCssClass': "drop-menu-dropdown",
112 'dropdownAutoWidth': true
118 'dropdownAutoWidth': true
113 };
119 };
114 $("#role").select2(select2Options);
120 $("#role").select2(select2Options);
115
121
116 var preloadData = {
122 var preloadData = {
117 results: [
123 results: [
118 % for entry in c.lifetime_values:
124 % for entry in c.lifetime_values:
119 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
125 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
120 % endfor
126 % endfor
121 ]
127 ]
122 };
128 };
123
129
124 $("#lifetime").select2({
130 $("#lifetime").select2({
125 containerCssClass: "drop-menu",
131 containerCssClass: "drop-menu",
126 dropdownCssClass: "drop-menu-dropdown",
132 dropdownCssClass: "drop-menu-dropdown",
127 dropdownAutoWidth: true,
133 dropdownAutoWidth: true,
128 data: preloadData,
134 data: preloadData,
129 placeholder: "${_('Select or enter expiration date')}",
135 placeholder: "${_('Select or enter expiration date')}",
130 query: function(query) {
136 query: function(query) {
131 feedLifetimeOptions(query, preloadData);
137 feedLifetimeOptions(query, preloadData);
132 }
138 }
133 });
139 });
134
140
135
141
136 var repoFilter = function(data) {
142 var repoFilter = function(data) {
137 var results = [];
143 var results = [];
138
144
139 if (!data.results[0]) {
145 if (!data.results[0]) {
140 return data
146 return data
141 }
147 }
142
148
143 $.each(data.results[0].children, function() {
149 $.each(data.results[0].children, function() {
144 // replace name to ID for submision
150 // replace name to ID for submision
145 this.id = this.repo_id;
151 this.id = this.repo_id;
146 results.push(this);
152 results.push(this);
147 });
153 });
148
154
149 data.results[0].children = results;
155 data.results[0].children = results;
150 return data;
156 return data;
151 };
157 };
152
158
153 $("#scope_repo_id_disabled").select2(select2Options);
159 $("#scope_repo_id_disabled").select2(select2Options);
154
160
155 var selectVcsScope = function() {
161 var selectVcsScope = function() {
156 // select vcs scope and disable input
162 // select vcs scope and disable input
157 $("#role").select2("val", "${c.role_vcs}").trigger('change');
163 $("#role").select2("val", "${c.role_vcs}").trigger('change');
158 $("#role").select2("readonly", true)
164 $("#role").select2("readonly", true)
159 };
165 };
160
166
161 $("#scope_repo_id").select2({
167 $("#scope_repo_id").select2({
162 cachedDataSource: {},
168 cachedDataSource: {},
163 minimumInputLength: 2,
169 minimumInputLength: 2,
164 placeholder: "${_('repository scope')}",
170 placeholder: "${_('repository scope')}",
165 dropdownAutoWidth: true,
171 dropdownAutoWidth: true,
166 containerCssClass: "drop-menu",
172 containerCssClass: "drop-menu",
167 dropdownCssClass: "drop-menu-dropdown",
173 dropdownCssClass: "drop-menu-dropdown",
168 formatResult: formatRepoResult,
174 formatResult: formatRepoResult,
169 query: $.debounce(250, function(query){
175 query: $.debounce(250, function(query){
170 self = this;
176 self = this;
171 var cacheKey = query.term;
177 var cacheKey = query.term;
172 var cachedData = self.cachedDataSource[cacheKey];
178 var cachedData = self.cachedDataSource[cacheKey];
173
179
174 if (cachedData) {
180 if (cachedData) {
175 query.callback({results: cachedData.results});
181 query.callback({results: cachedData.results});
176 } else {
182 } else {
177 $.ajax({
183 $.ajax({
178 url: pyroutes.url('repo_list_data'),
184 url: pyroutes.url('repo_list_data'),
179 data: {'query': query.term},
185 data: {'query': query.term},
180 dataType: 'json',
186 dataType: 'json',
181 type: 'GET',
187 type: 'GET',
182 success: function(data) {
188 success: function(data) {
183 data = repoFilter(data);
189 data = repoFilter(data);
184 self.cachedDataSource[cacheKey] = data;
190 self.cachedDataSource[cacheKey] = data;
185 query.callback({results: data.results});
191 query.callback({results: data.results});
186 },
192 },
187 error: function(data, textStatus, errorThrown) {
193 error: function(data, textStatus, errorThrown) {
188 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
194 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
189 }
195 }
190 })
196 })
191 }
197 }
192 })
198 })
193 });
199 });
194 $("#scope_repo_id").on('select2-selecting', function(e){
200 $("#scope_repo_id").on('select2-selecting', function(e){
195 selectVcsScope()
201 selectVcsScope()
196 });
202 });
197
203
198 });
204 });
199 </script>
205 </script>
@@ -1,204 +1,210 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <script>
4 <script>
5 var showAuthToken = function(authTokenId) {
5 var showAuthToken = function(authTokenId) {
6 return _showAuthToken(authTokenId, pyroutes.url('edit_user_auth_tokens_view', {'user_id': '${c.user.user_id}'}))
6 return _showAuthToken(authTokenId, pyroutes.url('edit_user_auth_tokens_view', {'user_id': '${c.user.user_id}'}))
7 }
7 }
8 </script>
8 </script>
9
9
10 <div class="panel-heading">
10 <div class="panel-heading">
11 <h3 class="panel-title">
11 <h3 class="panel-title">
12 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')}
12 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')}
13 &nbsp;- ${_('Authentication Tokens')}
13 &nbsp;- ${_('Authentication Tokens')}
14 </h3>
14 </h3>
15 </div>
15 </div>
16 <div class="panel-body">
16 <div class="panel-body">
17 <div class="apikeys_wrap">
17 <div class="apikeys_wrap">
18 <p>
18 <p>
19 ${_('Authentication tokens can be used to interact with the API, or VCS-over-http. '
19 ${_('Available roles')}:
20 'Each token can have a role. Token with a role can be used only in given context, '
20 <ul>
21 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
21 % for role in h.UserApiKeys.ROLES:
22 <li>
23 <span class="tag disabled">${h.UserApiKeys._get_role_name(role)}</span>
24 <span>${h.UserApiKeys._get_role_description(role) |n}</span>
25 </li>
26 % endfor
27 </ul>
22 </p>
28 </p>
23 <table class="rctable auth_tokens">
29 <table class="rctable auth_tokens">
24 <tr>
30 <tr>
25 <th>${_('Token')}</th>
31 <th>${_('Token')}</th>
26 <th>${_('Description')}</th>
32 <th>${_('Description')}</th>
27 <th>${_('Role')}</th>
33 <th>${_('Role')}</th>
28 <th>${_('Repository Scope')}</th>
34 <th>${_('Repository Scope')}</th>
29 <th>${_('Expiration')}</th>
35 <th>${_('Expiration')}</th>
30 <th>${_('Action')}</th>
36 <th>${_('Action')}</th>
31 </tr>
37 </tr>
32 %if c.user_auth_tokens:
38 %if c.user_auth_tokens:
33 %for auth_token in c.user_auth_tokens:
39 %for auth_token in c.user_auth_tokens:
34 <tr class="${('expired' if auth_token.expired else '')}">
40 <tr class="${('expired' if auth_token.expired else '')}">
35 <td class="td-authtoken">
41 <td class="td-authtoken">
36 <div class="user_auth_tokens">
42 <div class="user_auth_tokens">
37 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
43 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
38 ${auth_token.token_obfuscated}
44 ${auth_token.token_obfuscated}
39 </code>
45 </code>
40 </div>
46 </div>
41 </td>
47 </td>
42 <td class="td-wrap">${auth_token.description}</td>
48 <td class="td-wrap">${auth_token.description}</td>
43 <td class="td-tags">
49 <td class="td-tags">
44 <span class="tag disabled">${auth_token.role_humanized}</span>
50 <span class="tooltip tag disabled" title="${h.UserApiKeys._get_role_description(auth_token.role)}">${auth_token.role_humanized}</span>
45 </td>
51 </td>
46 <td class="td">${auth_token.scope_humanized}</td>
52 <td class="td">${auth_token.scope_humanized}</td>
47 <td class="td-exp">
53 <td class="td-exp">
48 %if auth_token.expires == -1:
54 %if auth_token.expires == -1:
49 ${_('never')}
55 ${_('never')}
50 %else:
56 %else:
51 %if auth_token.expired:
57 %if auth_token.expired:
52 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
58 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
53 %else:
59 %else:
54 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
60 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
55 %endif
61 %endif
56 %endif
62 %endif
57 </td>
63 </td>
58 <td class="td-action">
64 <td class="td-action">
59 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)}
65 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)}
60 <input name="del_auth_token" type="hidden" value="${auth_token.user_api_key_id}">
66 <input name="del_auth_token" type="hidden" value="${auth_token.user_api_key_id}">
61 <button class="btn btn-link btn-danger" type="submit"
67 <button class="btn btn-link btn-danger" type="submit"
62 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
68 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
63 ${_('Delete')}
69 ${_('Delete')}
64 </button>
70 </button>
65 ${h.end_form()}
71 ${h.end_form()}
66 </td>
72 </td>
67 </tr>
73 </tr>
68 %endfor
74 %endfor
69 %else:
75 %else:
70 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
76 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
71 %endif
77 %endif
72 </table>
78 </table>
73 </div>
79 </div>
74
80
75 <div class="user_auth_tokens">
81 <div class="user_auth_tokens">
76 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), request=request)}
82 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), request=request)}
77 <div class="form form-vertical">
83 <div class="form form-vertical">
78 <!-- fields -->
84 <!-- fields -->
79 <div class="fields">
85 <div class="fields">
80 <div class="field">
86 <div class="field">
81 <div class="label">
87 <div class="label">
82 <label for="new_email">${_('New authentication token')}:</label>
88 <label for="new_email">${_('New authentication token')}:</label>
83 </div>
89 </div>
84 <div class="input">
90 <div class="input">
85 ${h.text('description', class_='medium', placeholder=_('Description'))}
91 ${h.text('description', class_='medium', placeholder=_('Description'))}
86 ${h.hidden('lifetime')}
92 ${h.hidden('lifetime')}
87 ${h.select('role', '', c.role_options)}
93 ${h.select('role', '', c.role_options)}
88
94
89 % if c.allow_scoped_tokens:
95 % if c.allow_scoped_tokens:
90 ${h.hidden('scope_repo_id')}
96 ${h.hidden('scope_repo_id')}
91 % else:
97 % else:
92 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
98 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
93 % endif
99 % endif
94 </div>
100 </div>
95 <p class="help-block">
101 <p class="help-block">
96 ${_('Repository scope works only with tokens with VCS type.')}
102 ${_('Repository scope works only with tokens with VCS type.')}
97 </p>
103 </p>
98 </div>
104 </div>
99 <div class="buttons">
105 <div class="buttons">
100 ${h.submit('save',_('Add'),class_="btn")}
106 ${h.submit('save',_('Add'),class_="btn")}
101 ${h.reset('reset',_('Reset'),class_="btn")}
107 ${h.reset('reset',_('Reset'),class_="btn")}
102 </div>
108 </div>
103 </div>
109 </div>
104 </div>
110 </div>
105 ${h.end_form()}
111 ${h.end_form()}
106 </div>
112 </div>
107 </div>
113 </div>
108 </div>
114 </div>
109
115
110 <script>
116 <script>
111
117
112 $(document).ready(function(){
118 $(document).ready(function(){
113
119
114 var select2Options = {
120 var select2Options = {
115 'containerCssClass': "drop-menu",
121 'containerCssClass': "drop-menu",
116 'dropdownCssClass': "drop-menu-dropdown",
122 'dropdownCssClass': "drop-menu-dropdown",
117 'dropdownAutoWidth': true
123 'dropdownAutoWidth': true
118 };
124 };
119 $("#role").select2(select2Options);
125 $("#role").select2(select2Options);
120
126
121 var preloadData = {
127 var preloadData = {
122 results: [
128 results: [
123 % for entry in c.lifetime_values:
129 % for entry in c.lifetime_values:
124 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
130 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
125 % endfor
131 % endfor
126 ]
132 ]
127 };
133 };
128
134
129 $("#lifetime").select2({
135 $("#lifetime").select2({
130 containerCssClass: "drop-menu",
136 containerCssClass: "drop-menu",
131 dropdownCssClass: "drop-menu-dropdown",
137 dropdownCssClass: "drop-menu-dropdown",
132 dropdownAutoWidth: true,
138 dropdownAutoWidth: true,
133 data: preloadData,
139 data: preloadData,
134 placeholder: "${_('Select or enter expiration date')}",
140 placeholder: "${_('Select or enter expiration date')}",
135 query: function(query) {
141 query: function(query) {
136 feedLifetimeOptions(query, preloadData);
142 feedLifetimeOptions(query, preloadData);
137 }
143 }
138 });
144 });
139
145
140
146
141 var repoFilter = function(data) {
147 var repoFilter = function(data) {
142 var results = [];
148 var results = [];
143
149
144 if (!data.results[0]) {
150 if (!data.results[0]) {
145 return data
151 return data
146 }
152 }
147
153
148 $.each(data.results[0].children, function() {
154 $.each(data.results[0].children, function() {
149 // replace name to ID for submision
155 // replace name to ID for submision
150 this.id = this.repo_id;
156 this.id = this.repo_id;
151 results.push(this);
157 results.push(this);
152 });
158 });
153
159
154 data.results[0].children = results;
160 data.results[0].children = results;
155 return data;
161 return data;
156 };
162 };
157
163
158 $("#scope_repo_id_disabled").select2(select2Options);
164 $("#scope_repo_id_disabled").select2(select2Options);
159
165
160 var selectVcsScope = function() {
166 var selectVcsScope = function() {
161 // select vcs scope and disable input
167 // select vcs scope and disable input
162 $("#role").select2("val", "${c.role_vcs}").trigger('change');
168 $("#role").select2("val", "${c.role_vcs}").trigger('change');
163 $("#role").select2("readonly", true)
169 $("#role").select2("readonly", true)
164 };
170 };
165
171
166 $("#scope_repo_id").select2({
172 $("#scope_repo_id").select2({
167 cachedDataSource: {},
173 cachedDataSource: {},
168 minimumInputLength: 2,
174 minimumInputLength: 2,
169 placeholder: "${_('repository scope')}",
175 placeholder: "${_('repository scope')}",
170 dropdownAutoWidth: true,
176 dropdownAutoWidth: true,
171 containerCssClass: "drop-menu",
177 containerCssClass: "drop-menu",
172 dropdownCssClass: "drop-menu-dropdown",
178 dropdownCssClass: "drop-menu-dropdown",
173 formatResult: formatRepoResult,
179 formatResult: formatRepoResult,
174 query: $.debounce(250, function(query){
180 query: $.debounce(250, function(query){
175 self = this;
181 self = this;
176 var cacheKey = query.term;
182 var cacheKey = query.term;
177 var cachedData = self.cachedDataSource[cacheKey];
183 var cachedData = self.cachedDataSource[cacheKey];
178
184
179 if (cachedData) {
185 if (cachedData) {
180 query.callback({results: cachedData.results});
186 query.callback({results: cachedData.results});
181 } else {
187 } else {
182 $.ajax({
188 $.ajax({
183 url: pyroutes.url('repo_list_data'),
189 url: pyroutes.url('repo_list_data'),
184 data: {'query': query.term},
190 data: {'query': query.term},
185 dataType: 'json',
191 dataType: 'json',
186 type: 'GET',
192 type: 'GET',
187 success: function(data) {
193 success: function(data) {
188 data = repoFilter(data);
194 data = repoFilter(data);
189 self.cachedDataSource[cacheKey] = data;
195 self.cachedDataSource[cacheKey] = data;
190 query.callback({results: data.results});
196 query.callback({results: data.results});
191 },
197 },
192 error: function(data, textStatus, errorThrown) {
198 error: function(data, textStatus, errorThrown) {
193 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
199 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
194 }
200 }
195 })
201 })
196 }
202 }
197 })
203 })
198 });
204 });
199 $("#scope_repo_id").on('select2-selecting', function(e){
205 $("#scope_repo_id").on('select2-selecting', function(e){
200 selectVcsScope()
206 selectVcsScope()
201 });
207 });
202
208
203 });
209 });
204 </script>
210 </script>
General Comments 0
You need to be logged in to leave comments. Login now