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